Introducción: Taller de Visualización de Datos

En este corto taller vamos a aprender las herramientas básicas para visualizar su data. La visualización de datos, ampliamente entendida, es una manera (efectiva, cuando se la hace bien) de contar una historia (la historia que tú quieres contar). Una mala visualización de datos es como una mala narrativa. Una buena visualización de datos es chefs kiss.

A manera resumida, hay dos tipos de visualización de datos:

Tomando en cuenta estas dos categorías, es importante mantener en algún lado de su psiquis las siguientes características cuando estén desarrollando sus gráficos:

  1. Visualizaciones explorativas
    1. Manten la mayor cantidad de detalle posible
    2. Limita el gráfico a lo que puedes ver e interpretar
  2. Visualizaciones explicativas
    1. Requiere de decisiones editoriales (tuyas)
    2. Resalta las características que sirvan para contar tu historia
    3. Elimina detalles superfluos

En el resto de este taller, vamos a utilizar R para que de tus números puedas sacar una historia (o los principios de una historia). Este taller no va a cubrir teorías sobre visualización de datos. Para eso necesitaríamos bastante más tiempo. Sin embargo, sí recomiendo que lean al respecto. Aunque se que igual no lo van a hacer, una buena introducción al tema es Wilde, Clause (2019) - Fundamentals of Data Visualization.

Parte 1: Su Data, tidyverse, y ggplot2

En la primera parte vamos a ver diferentes formatos de data, cómo cargarlos a R, entender la forma de sus bases de datos, entender la forma que ggplot2 prefiere en que estén sus datos, cómo crear variables, cómo modificar sus bases de datos, y, finalmente, la intuición básica para crear gráficos en R.

Para este taller vamos a utilizar, principalmente, dos paquetes de R con los cuales deberían estar familiarizadxs: tidyverse y ggplot2. tidyverse es un paquete que nos ayuda a manipular nuestros datos (por ej., eliminar variables, agregar observaciones, unir bases de datos). Tiene un lenguaje particular: pipes. Como este no es un taller de tidyverse y ustedes son estudiantes del Tec y pagan una millonada por su educación, voy a asumir que todxs tienen un conocimiento básico de tidyverse. Si quieren repasar cómo funciona este paquete, pueden revisar el siguiente enlace. ggplot2 está incorporado a tidyverse, y es una herramienta bastante desarrollada, intuitiva (cuando le agarras la vuelta), y con una gramática consistente. En este taller vamos a adentrarnos en la lógica de ggplot2. Sin embargo, no lograremos cubrir absolutamente todo lo que se puede hacer con ggplot2 en específico, ni con R en general. Para leer más sobre la gramática de ggplot, recomiendo que lean Wickham, Hadlye (2010) - A Layered Grammar of Graphics. Para resolver sus preguntas sobre ggplot2, recomiendo que exploren las siguiente páginas:

¡Comencemos!

Importar Data

Voy a asumir que ya tienen una base de datos. Lo primero que van a tener que hacer es importar esa base de datos a R. Las bases de datos vienen en todo tipo de formas y colores, pero sobre todo, en diferentes formatos. Nos vamos a enfocar en los siguientes formatos:

  • .Rdata: este es el formato propio de R para guardar información.
    • Lo bueno: puede almancener dataframes, al igual que otros objetos creados en R. Es bueno para compactar la información, por lo tanto utiliza menos espacio en el disco. Es el formato que R lee más rápido.
    • Lo malo: una base de datos guardada en este formato no puede ser abierta por otros programas (por ej., Excel).
  • .csv: comma-separated values.
    • Lo bueno: es bastante común y puede ser abierto por la mayoría de programas que manejan bases de datos. También puede venir como extensión .txt.
    • Lo malo: Utiliza más espacio de disco que .Rdata y .xlsx. No es buena para manejar bases de datos que contienen texto.
  • .xlsx: una versión comprimida de .csv.
    • Lo bueno: Utiliza menos espacio de disco que .csv. Es bastante común y puede ser abierto por la mayoría de programas que manejan bases de datos.
    • Lo malo: Utiliza más espacio de disco que .Rdata.
  • .tsv: tab-separated values. Ver .csv.

Otras extensiones:

  • .sav: extensión de data producida por SPSS.
  • .dta: extensión de data producida por Stata.

Existen otras extensiones. Para casi todas hay un paquete en R y una rápida búsqueda en el internet les dará la respuesta.

Importar un archivo con extensión .Rdata es lo más simple:

setwd("path_directorio/carpeta/") # No te olvides de indicar en qué directorio se encuentra tu data.

load("tu_data.Rdata") 

El paquete readr tiene las funciones necesarias para importar archivos con extensión .csv o .txt:

library(readr)
setwd("path_directorio/carpeta/")

# Importar data de una archivo .csv
Salarios <- read_csv("salarios.csv")

# Importar data de una archivo .tsv o .txt
Salarios <- read_tsv("salarios.txt")
Salarios <- read_tsv("salarios.tsv")

Esta función asume que la primera línea de tu base de datos contiene el nombre de las variables, los valores están separados por una coma o por tabs, y los NAs están representados por celdas vacías.

El paquete `readxl`` tiene las funciones necesarias para importar archivos con extensión .xls o .xlsx:

library(readxl)
setwd("path_directorio/carpeta/")

# Importar data de una archivo .xls o .xlsx
Salarios <- read_excel("salarios.xls",
                     sheet = "sheet1") # Necesario únicamente si la data esta en una hoja que no sea la primera
Salarios <- read_excel("salarios.xlsx",
                     sheet = "sheet1")

El paquete `readstata13`` tiene las funciones necesarias para importar archivos con extensión .dta:

library(readstata13)
setwd("path_directorio/carpeta/")

# Importar data de una archivo .dta
Salarios <- read.dta13("salarios.dta")

Finalmente, el paquete `foreign`` tiene las funciones necesarias para importar archivos con extensión .sav:

library(foreign)
setwd("path_directorio/carpeta/")

# Importar data de una archivo .sav
dataset = read.spss("salarios.sav", 
                    to.data.frame=TRUE) # Necesario!

Limpieza de Datos y la Unidad de Análisis

Una vez que tienen cargada su base de datos la podemos comenzar a manipular. En esta sección vamos a ver dos conceptos: wrangling o la transformación de su base de datos, y el shape de su base de datos y qué forma es la adecuada para utilizar ggplot2.

Data Wrangling

La manipulación y transformación de la base de datos es el primer paso antes de iniciar su visualización. Recuerden, toda nueva variable que ustedes están creando será un elemento más de la historia que quieran contar. Esto no significa que TODAS las variables que creen (eliminen) van a terminar dentro (fuera) de un gráfico. Solo significa que las variables que ustedes creen eventualmente tendrán un propósito, directo o indirecto, en la historia que cuenten.

Si pensamos a las bases de datos como un matriz, la manipulación de la data se la puede hacer en dos dimensiones: filas (observaciones/eje y) o columnas (variables/eje x). Comencemos con la manipulación de las columnas/variables.

Seleccionar, Renombrar, Crear Variables

La función select del paquete tidyverse nos permite limitar o seleccionar las variables de nuestra base de datos.

library(tidyverse)

# Mantener las variables nombre, estatura y género
nueva_data <- vieja_data %>%
  select(nombre, estatura, genero)

# Mantener las variables nombre, y todas las que se encuentren entre masa y especie
nueva_data <- vieja_data %>%
  select(nombre, masa:especie)

# Mantener todas las variables excepto edad y genero
nueva_data <- vieja_data %>%
  select(-edad, -genero)

Podemos utilizar colnames para ver el nombre de la variables y también para renombrar las variables.

# Ver el nombre de las variables de la base de datos starwars*
colnames(starwars)
##  [1] "name"       "height"     "mass"       "hair_color" "skin_color"
##  [6] "eye_color"  "birth_year" "sex"        "gender"     "homeworld" 
## [11] "species"    "films"      "vehicles"   "starships"
# *starwars es una base de datos de muestra que está incluida en el librería tidyverse
# Renombrar la primer variable
colnames(starwars)[1] <- "nombre"
colnames(starwars)
##  [1] "nombre"     "height"     "mass"       "hair_color" "skin_color"
##  [6] "eye_color"  "birth_year" "sex"        "gender"     "homeworld" 
## [11] "species"    "films"      "vehicles"   "starships"
# Renombrar de la primer a la cuarta variable
colnames(starwars)[c(1:4)] <- c("nombre","estatura","masa","color_pelo")
colnames(starwars)
##  [1] "nombre"     "estatura"   "masa"       "color_pelo" "skin_color"
##  [6] "eye_color"  "birth_year" "sex"        "gender"     "homeworld" 
## [11] "species"    "films"      "vehicles"   "starships"

Filtrar Observaciones

También habrán ocasiones en las que quieran eliminar ciertas filas/observaciones o filas que tengan ciertas características. Para eliminar observaciones manualmente podemos usar las funciones base de R. Veamos cómo se ve nuestra base de datos.

# Ver data
head(starwars) # Nota: solo se muestra una parte de la base de datos
## # A tibble: 6 × 14
##   nombre  estatura  masa color_pelo skin_color eye_color birth_year sex   gender
##   <chr>      <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
## 1 Luke S…      172    77 blond      fair       blue            19   male  mascu…
## 2 C-3PO        167    75 <NA>       gold       yellow         112   none  mascu…
## 3 R2-D2         96    32 <NA>       white, bl… red             33   none  mascu…
## 4 Darth …      202   136 none       white      yellow          41.9 male  mascu…
## 5 Leia O…      150    49 brown      light      brown           19   fema… femin…
## 6 Owen L…      178   120 brown, gr… light      blue            52   male  mascu…
## # ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
## #   vehicles <list>, starships <list>

Listo. Si quisieran eliminar solo la segunda observación, podrían hacer lo siguiente:

# Eliminar manualmente la segunda observación
starwars_no2 <- starwars[-2,]
head(starwars_no2)
## # A tibble: 6 × 14
##   nombre  estatura  masa color_pelo skin_color eye_color birth_year sex   gender
##   <chr>      <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
## 1 Luke S…      172    77 blond      fair       blue            19   male  mascu…
## 2 R2-D2         96    32 <NA>       white, bl… red             33   none  mascu…
## 3 Darth …      202   136 none       white      yellow          41.9 male  mascu…
## 4 Leia O…      150    49 brown      light      brown           19   fema… femin…
## 5 Owen L…      178   120 brown, gr… light      blue            52   male  mascu…
## 6 Beru W…      165    75 brown      light      blue            47   fema… femin…
## # ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
## #   vehicles <list>, starships <list>

También podemos eliminar de las segunda a la cuarta fila:

# Eliminar manualmente de la segunda a la cuarta observación
starwars_no24 <- starwars[-c(2:4),]
head(starwars_no24)
## # A tibble: 6 × 14
##   nombre  estatura  masa color_pelo skin_color eye_color birth_year sex   gender
##   <chr>      <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
## 1 Luke S…      172    77 blond      fair       blue              19 male  mascu…
## 2 Leia O…      150    49 brown      light      brown             19 fema… femin…
## 3 Owen L…      178   120 brown, gr… light      blue              52 male  mascu…
## 4 Beru W…      165    75 brown      light      blue              47 fema… femin…
## 5 R5-D4         97    32 <NA>       white, red red               NA none  mascu…
## 6 Biggs …      183    84 black      light      brown             24 male  mascu…
## # ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
## #   vehicles <list>, starships <list>

Por lo general, no es una manera muy práctica de filtrar información. Una mejor manera es filtrar la información de acuerdo a ciertas características que encontramos en la propia data. Para eso utilizamos la función filter.

# Quedarse únicamente con observaciones cuya homeworld sea "Tatooine"
tatooine <- starwars %>%
  filter(homeworld=="Tatooine")
## filter: removed 77 rows (89%), 10 rows remaining
head(tatooine)
## # A tibble: 6 × 14
##   nombre  estatura  masa color_pelo skin_color eye_color birth_year sex   gender
##   <chr>      <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
## 1 Luke S…      172    77 blond      fair       blue            19   male  mascu…
## 2 C-3PO        167    75 <NA>       gold       yellow         112   none  mascu…
## 3 Darth …      202   136 none       white      yellow          41.9 male  mascu…
## 4 Owen L…      178   120 brown, gr… light      blue            52   male  mascu…
## 5 Beru W…      165    75 brown      light      blue            47   fema… femin…
## 6 R5-D4         97    32 <NA>       white, red red             NA   none  mascu…
## # ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
## #   vehicles <list>, starships <list>
# Eliminar únicamente observaciones cuya homeworld sea "Tatooine"
no_tatooine <- starwars %>%
  filter(homeworld!="Tatooine")
## filter: removed 20 rows (23%), 67 rows remaining
head(no_tatooine)
## # A tibble: 6 × 14
##   nombre  estatura  masa color_pelo skin_color eye_color birth_year sex   gender
##   <chr>      <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
## 1 R2-D2         96    32 <NA>       white, bl… red               33 none  mascu…
## 2 Leia O…      150    49 brown      light      brown             19 fema… femin…
## 3 Obi-Wa…      182    77 auburn, w… fair       blue-gray         57 male  mascu…
## 4 Wilhuf…      180    NA auburn, g… fair       blue              64 male  mascu…
## 5 Chewba…      228   112 brown      unknown    blue             200 male  mascu…
## 6 Han So…      180    80 brown      fair       brown             29 male  mascu…
## # ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
## #   vehicles <list>, starships <list>

Crear/Modificar Variables, Agrupar, y Agregar/Recortar Datos

La función para crear/modificar variables es mutate.

# Pasar la altura que está en centímetros a pulgadas y la masa que está en kilogramos a libras
newdata <- mutate(starwars, 
                  estatura = estatura * 0.394,
                  masa   = masa   * 2.205)

# También podemos crear nuevas variables
newdata <- starwars %>%
  mutate(estatura_in = estatura * 0.394,
         masa_lb   = masa   * 2.205)

Por lo general, vamos a crear variables basadas en condiciones de otras variables en nuestra data.

# Si la altura es mayor a 180 cm heightcat = "alto", caso contratio heightcat = "chaparro",   
newdata <- mutate(starwars, 
                  estaturacat = ifelse(estatura > 180, 
                                     "alto", 
                                     "chaparro")
                  
# Convertir cualquier color de ojos que nos sea black, blue o brown, en "otro"
newdata <- mutate(starwars, 
                  eye_color = ifelse(eye_color %in% c("black", "blue", "brown"),
                                     eye_color,
                                     "otro")
                  
# Cualquier estatura más que 200 y menos que 75 la pasamos a NA
newdata <- mutate(starwars, 
                  estatura = ifelse(estatura < 75 | estatura > 200,
                                     NA,
                                     estatura)

También podemos crear nuevas variables que sean producto de operaciones. Incluso nuevas variables que sean productos de operaciones de datos previamente agrupados.

# Calcular el promedio de estatura de los personajes en la base de datos
starwars %>%
  mutate(mean_est = mean(estatura, na.rm = TRUE)) %>%
  select(mean_est) 
## mutate: new variable 'mean_est' (double) with one unique value and 0% NA
## select: dropped 14 variables (nombre, estatura, masa, color_pelo, skin_color, …)
## # A tibble: 87 × 1
##    mean_est
##       <dbl>
##  1     175.
##  2     175.
##  3     175.
##  4     175.
##  5     175.
##  6     175.
##  7     175.
##  8     175.
##  9     175.
## 10     175.
## # ℹ 77 more rows
# Es los mismo que hacer esto:
mean(starwars$estatura, na.rm = TRUE)
## [1] 174.6049
# Calcular el promedio de estatura por especie:
starwars %>%
  group_by(species) %>%
  mutate(mean_est = mean(estatura, na.rm = TRUE)) %>%
  select(species,mean_est,estatura) 
## group_by: one grouping variable (species)
## mutate (grouped): new variable 'mean_est' (double) with 30 unique values and 0% NA
## select: dropped 12 variables (nombre, masa, color_pelo, skin_color, eye_color, …)
## # A tibble: 87 × 3
## # Groups:   species [38]
##    species mean_est estatura
##    <chr>      <dbl>    <int>
##  1 Human       178       172
##  2 Droid       131.      167
##  3 Droid       131.       96
##  4 Human       178       202
##  5 Human       178       150
##  6 Human       178       178
##  7 Human       178       165
##  8 Droid       131.       97
##  9 Human       178       183
## 10 Human       178       182
## # ℹ 77 more rows

Hay ocasiones en las que no queremos mantener todas esas observaciones repetidas para esa variable en particular. Para esto, utilizamos la función distinct.

# Eliminar observaciones repetidas de especie
starwars %>%
  group_by(species) %>%
  mutate(estatura_ht = mean(estatura, na.rm = TRUE)) %>%
  distinct(species, .keep_all=TRUE) %>%
  select(species,estatura_ht) 
## group_by: one grouping variable (species)
## mutate (grouped): new variable 'estatura_ht' (double) with 30 unique values and 0% NA
## distinct (grouped): removed 49 rows (56%), 38 rows remaining
## select: dropped 13 variables (nombre, estatura, masa, color_pelo, skin_color, …)
## # A tibble: 38 × 2
## # Groups:   species [38]
##    species        estatura_ht
##    <chr>                <dbl>
##  1 Human                 178 
##  2 Droid                 131.
##  3 Wookiee               231 
##  4 Rodian                173 
##  5 Hutt                  175 
##  6 <NA>                  175 
##  7 Yoda's species         66 
##  8 Trandoshan            190 
##  9 Mon Calamari          180 
## 10 Ewok                   88 
## # ℹ 28 more rows

La Unidad de Análisis

Lo primero que deben saber de su data es la unidad de analásis: la unidad principal a la que se está observando en una base de datos. Por ejemplo, en la base de datos de starwars, la unidad de análisis era el personaje. Toda la información de las variables era sobre el personaje.

# Ver data
head(starwars) # Nota: solo se muestra una parte de la base de datos
## # A tibble: 6 × 14
##   nombre  estatura  masa color_pelo skin_color eye_color birth_year sex   gender
##   <chr>      <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
## 1 Luke S…      172    77 blond      fair       blue            19   male  mascu…
## 2 C-3PO        167    75 <NA>       gold       yellow         112   none  mascu…
## 3 R2-D2         96    32 <NA>       white, bl… red             33   none  mascu…
## 4 Darth …      202   136 none       white      yellow          41.9 male  mascu…
## 5 Leia O…      150    49 brown      light      brown           19   fema… femin…
## 6 Owen L…      178   120 brown, gr… light      blue            52   male  mascu…
## # ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
## #   vehicles <list>, starships <list>

En la siguiente base de datos, la unidad de análisis es el país-año. Es decir, la información de cada variable es sobre un país en un año específico.

library(readstata13)

panel <- read.dta13("http://dss.princeton.edu/training/Panel101.dta")
## Warning in read.dta13("http://dss.princeton.edu/training/Panel101.dta"): 
##    Factor codes of type double or float detected in variables
## 
##    opinion
## 
##    No labels have been assigned.
##    Set option 'nonint.factors = TRUE' to assign labels anyway.
head(panel,20)
##    country year           y y_bin          x1         x2          x3 opinion op
## 1        A 1990  1342787840     1  0.27790365 -1.1079559  0.28255358       1  1
## 2        A 1991 -1899660544     0  0.32068470 -0.9487200  0.49253848       3  0
## 3        A 1992   -11234363     0  0.36346573 -0.7894840  0.70252335       3  0
## 4        A 1993  2645775360     1  0.24614404 -0.8855330 -0.09439092       3  0
## 5        A 1994  3008334848     1  0.42462304 -0.7297683  0.94613063       3  0
## 6        A 1995  3229574144     1  0.47721413 -0.7232460  1.02968037       1  1
## 7        A 1996  2756754176     1  0.49980500 -0.7815716  1.09228814       3  0
## 8        A 1997  2771810560     1  0.05162839 -0.7048455  1.41590083       1  1
## 9        A 1998  3397338880     1  0.36641079 -0.6983712  1.54872274       3  0
## 10       A 1999    39770336     1  0.39584252 -0.6431540  1.79419804       4  0
## 11       B 1990 -5934699520     0 -0.08184998  1.4251202  0.02342812       2  1
## 12       B 1991  -711623744     0  0.10616001  1.6496018  0.26036251       1  1
## 13       B 1992 -1933116160     0  0.35378519  1.5937191 -0.23439877       2  1
## 14       B 1993  3072741632     1  0.72677696  1.6917576  0.25622433       4  0
## 15       B 1994  3768078848     1  0.71939486  1.7414261  0.41174951       3  0
## 16       B 1995  2837581312     1  0.67154658  1.7083139  0.53584301       4  0
## 17       B 1996   577199360     1  0.81985730  1.5324961 -0.49964902       1  1
## 18       B 1997  1786851584     1  0.88016719  1.5021962 -0.57626772       3  0
## 19       B 1998  -149072048     0  0.70451611  1.4236463 -0.44841924       2  1
## 20       B 1999 -1174480128     0  0.23696731  1.4545859 -0.04936399       4  0

Podemos modificar la unidad de análisis utilizando una de las técnicas presentadas antes. Por ejemplo, podemos cambiar la unidad de análisis de nuestra base de datos panel al año:

panel %>%
  group_by(year) %>%
  mutate(y_mean_year = mean(y, na.rm=TRUE)) %>%
  distinct(year,.keep_all=T) %>%
  select(year,y_mean_year)
## group_by: one grouping variable (year)
## mutate (grouped): new variable 'y_mean_year' (double) with 10 unique values and 0% NA
## distinct (grouped): removed 60 rows (86%), 10 rows remaining
## select: dropped 8 variables (country, y, y_bin, x1, x2, …)
## # A tibble: 10 × 2
## # Groups:   year [10]
##     year y_mean_year
##    <int>       <dbl>
##  1  1990    3871104 
##  2  1991  621134802.
##  3  1992  650395397.
##  4  1993 3201693936 
##  5  1994 3662756754.
##  6  1995 1543687525.
##  7  1996 2416958654.
##  8  1997 3724919991.
##  9  1998  997817328 
## 10  1999 1627486320

Lo primero que deben hacer antes de comenzar a evaluar la data, es saber la unidad de análisis. Es decir, de quién estamos hablando cuando vamos a contar nuestra historia.

Introducción a ggplot2

ggplot2 es un paquete que está incluído en tidyverse y nos permite crear gráficos en capas. Podemos construir un gráfico complejo comenzando por algo simple e ir agregando elementos, uno a la vez.

Para construir un gráfico la primera función que necesitas es ggplot que especifica

  1. La data que vas a utilizar
  2. El mapeo (mapping) de las variables a propiedades visuales. El mapping se lo pone dentro de la función aes (que significa aesthetics).
# Carga base de datos
data(CPS85 , package = "mosaicData")

# Especificar la data y el mapeo
CPS85 %>% # Esto es suficiente para especificar la data
  ggplot(mapping = aes(x = exper, y = wage))

# Lo mismo que antes pero escrito diferente
# ggplot(data = CPS85, mapping = aes(x = exper, y = wage))

No hay nada. ¿Por qué? Especificamos que la variable exper debe estar mapeada en el eje x y que la variable wage debe estar en el eje y, pero no hemos especificado qué queremos poner en ese gráfico.

geoms

Los geoms son objetos geométricos (puntos, líneas, barras, etc.) que pueden ser colocados en un gráfico. Se las agrega utilizanso funciones que comienzan con geom_. En este ejemplo, vamos a agregar puntos usando la función geom_point y así crearemos un scatterplot.

En ggplot2, las funciones están enlazadas utilizando un + para construir el gráfico final.

CPS85 %>% 
  ggplot(mapping = aes(x = exper, y = wage)) +
  geom_point()

¿Quejeso? ¿Un outlier? Pues lo eliminamos:

CPS85 %>% 
  filter(wage<30) %>%
  ggplot(mapping = aes(x = exper, y = wage))+
  geom_point()
## filter: removed one row (<1%), 533 rows remaining

Belleza. Una serie de opciones pueden ser modificadas dentro de un geom_. Estas incluyen color, size, y alpha. Estos controlan el color, el tamaño y la transparencia. La transparencia va de 0 (completamente transparente) a 1 (completamente opaco). La transparencia sirve para visualizar mejor los elementos que se superponen (y además hace que sus gráficos se vean bonitos).

CPS85 %>% 
  filter(wage<30) %>%
  ggplot(aes(x = exper, y = wage)) +
  geom_point(color = "cornflowerblue",
             alpha = .7,
             size = 3)
## filter: removed one row (<1%), 533 rows remaining

Ahora, podemos agregar la recta de mejor ajuste (best fit line) utilizando la función geom_smooth. Las opciones para cada geom_ las pueden consultar utilizando el comando help(). En el caso de geom_smooth podemos graficar una línea basada en un modelo lineal (lm).

CPS85 %>% 
  filter(wage<30) %>%
  ggplot(aes(x = exper, y = wage)) +
  geom_point(color = "cornflowerblue",
             alpha = .7,
             size = 3) +
  geom_smooth(method = "lm",
              color = "grey")
## filter: removed one row (<1%), 533 rows remaining
## `geom_smooth()` using formula = 'y ~ x'

Incrementar la experiencia está correlacionado con incrementos en el salario.

Agrupar por Características de la Data

Además de mapear las variable al eje x y al eje y, las variables pueden ser mapeadas a un color, shape, size, alpha, y otras características visuales de objetos geométricos. Estos nos permite diferenciar grupos de observaciones en el mismo gráfico. Agreguemos el género en el gráfico y representémoslo por color.

CPS85 %>% 
  filter(wage<30) %>%
  ggplot(aes(x = exper,
             y = wage,
             color = sex)) +
  geom_point(alpha = .7,
             size = 3) +
  geom_smooth(method = "lm", 
              size = 1.5)
## filter: removed one row (<1%), 533 rows remaining
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `geom_smooth()` using formula = 'y ~ x'

La opción de color la ponemos dentro de la funcion aes porque estamos mapeando la variable a un aesthetic.

Parecería que los hombres tienden a ganar más dinero que las mujeres y que la relación entre experiencia y salario es más fuerte para hombres que para mujeres. Patriarcado que le llaman.

Scales

Las scales controlan las características visuales del gráfico. Las funciones scales_ te permiten modificar el mapeo.

CPS85 %>% 
  filter(wage<30) %>%
  ggplot(aes(x = exper,
             y = wage,
             color = sex)) +
  geom_point(size = 3) +
  geom_smooth(method = "lm", 
              size = 1.5) +
  scale_x_continuous(breaks = seq(0, 60, 10)) +
  scale_y_continuous(breaks = seq(0, 30, 5),
                     label = scales::dollar) +
  scale_color_viridis_d(option="E",alpha = 0.5)
## filter: removed one row (<1%), 533 rows remaining
## `geom_smooth()` using formula = 'y ~ x'

Ya se ve mejor. Hay unos signos de dólares y los colores están más bonitos. Digamos que queremos ver si la relación de género entre experiencia y salario se mantiene para todos los sectores. Para eso utilizamos…

Facets

Facets reproducen el gráfico para cada nivel de una variable (o algunas variables). En este caso, los gráficos estarán divididos para cada nivel de la variable sector.

CPS85 %>% 
  filter(wage<30) %>%
  ggplot(aes(x = exper,
             y = wage,
             color = sex)) +
  geom_point(size = 3) +
  geom_smooth(method = "lm", 
              size = 1.5) +
  scale_x_continuous(breaks = seq(0, 60, 10)) +
  scale_y_continuous(breaks = seq(0, 30, 5),
                     label = scales::dollar) +
  scale_color_viridis_d(option="E",alpha = 0.5) +
  facet_wrap(~sector)
## filter: removed one row (<1%), 533 rows remaining
## `geom_smooth()` using formula = 'y ~ x'

Labels

Un gráfico debe ser fácil de interpretar. El lector no tiene que estar adivinando cada nombre que se les ocurrió poner a las variables ni qué significa cada eje ni cada color ni cada movida. La función labs nos permite cambiar eso.

CPS85 %>% 
  filter(wage<30) %>%
  ggplot(aes(x = exper,
             y = wage,
             color = sex)) +
  geom_point(size = 3) +
  geom_smooth(method = "lm", 
              size = 1.5) +
  scale_x_continuous(breaks = seq(0, 60, 10)) +
  scale_y_continuous(breaks = seq(0, 30, 5),
                     label = scales::dollar) +
  scale_color_viridis_d(option="E",alpha = 0.5) +
  facet_wrap(~sector) +
  labs(title = "Relación entre salario y experiencia",
       subtitle = "Encuesta de Población",
       caption = "fuente: http://mosaic-web.org/",
       x = "Años de Experiencia",
       y = "Salario por Hora",
       color = "Género")
## filter: removed one row (<1%), 533 rows remaining
## `geom_smooth()` using formula = 'y ~ x'

Temas

Finalmente, le podemos dar los últimos toques para que los gráficos queden pepa, para que tenga ese je ne sais quoi, es something something. La funcion theme_ nos permite controlar colores del background, fuentes, líneas, leyendas, y otros elementos que no están relacionados a nuestra data. El gráfico final se puede ver algo así.

CPS85 %>% 
  filter(wage<30) %>%
  ggplot(aes(x = exper,
             y = wage,
             color = sex,
             fill = sex)) +
  geom_point(size = 3) +
  geom_smooth(method = "lm", 
              size = 1.5) +
  scale_x_continuous(breaks = seq(0, 60, 10)) +
  scale_y_continuous(breaks = seq(0, 30, 5),
                     label = scales::dollar) +
  scale_color_viridis_d(option="E",alpha = 0.5) +
  scale_fill_viridis_d(option="E",alpha = 0.5) +
  facet_wrap(~sector) +
  labs(title = "Relación entre salario y experiencia",
       subtitle = "Encuesta de Población",
       caption = "fuente: http://mosaic-web.org/",
       x = "Años de Experiencia",
       y = "Salario por Hora",
       color = "Género",
       fill = 'Género') +
  theme_minimal() +
  theme(legend.position = "bottom") 
## filter: removed one row (<1%), 533 rows remaining
## `geom_smooth()` using formula = 'y ~ x'

¡BE-LLE-ZA!

Ahora estamos listos para explorar la data y visualizarla.

Parte 2: Gráficos y Para Qué Sirven

Hay gráficos para todo, más de los que lograremos cubrir en el tiempo que tenemos. Los gráficos se adaptan, no solo a lo que quieren mostrar, sino al tipo y la cantidad de variables que quieren incluir. Hay dos tipos de variables que vamos a cubrir:

Vamos a comenzar con gráficos que describen una variable (tanto categórica como continua), y luego pasaremos a gráficos que describen dos. Si bien hay gráficos que describen tres variables, esos no los vamos a cubrir en este taller. Noten que describir una o dos variables no significa que únicamente vamos a utilizar una o dos variables para proporcionar información al gráfico.

Gráficos Univariados

Los gráficos univariados muestran la distribución de la data de una sola variable.

Variables Categóricas

La distribución de una sola variable categórica por lo general se la grafica en un gráfico de barras. También en un pie chart, pero esos son horribles y no sirven para nada entonces me niego a enseñarlo.

Gráficos de Barras

La base de datos Marriage contiene registros de matrimonios de 98 individuos de Alabama (unidad de análisis). Primero vamos a mostras un gráfico de barras que muestra la distribución de los participantes por signo zodiacal.

library(ggplot2)
data(Marriage, package = "mosaicData")

# Distribución por zodiaco
Marriage %>%
  ggplot(aes(x = sign)) + 
  geom_bar()

La mayoría son piscis, obvio.

Podemos modificar el fill y el color de cada barra, los labels, y el title añadiendo opciones a la funcion geom_bar.

Marriage %>%
  ggplot(aes(x = sign)) + 
  geom_bar(fill = "cornflowerblue", 
           color="black",
           alpha = .7) +
  labs(x = "Zodiaco", 
       y = "Frecuencia", 
       title = "Individuos por zodiaco")

La barras pueden representar porcentajes en lugar de frecuencias. Para los gráficos de barras, el código aes(x=sign) es de hecho un atajo para aes(x = sign, y = ..count..), donde ..count.. es una variable especial que representa la frecuencia de cada categoría. Podemos calcular los porcentajes y mapearlos a nuestra variable y. Sin embargo, también debemos cambiar el geom a geom_col o un geom para graficar columnas (geom_bar es un subfunción de geom_col).

Marriage %>%
  mutate(total_base = n()) %>% # Calcula el total de observaciones de la base
  group_by(sign) %>%
  mutate(total_signo = n(), # Calcula el total de observaciones por zodiaco
         pct_signo = total_signo/total_base) %>% # Calcula el porcentaje
  # Como ahora mi unidad de analisis es el signo, entonces tenemos que cambiar la base de datos a esa unidad:
  distinct(sign, .keep_all=T) %>%
  ggplot(aes(x = sign, y = pct_signo)) + 
  geom_col(fill = "cornflowerblue", 
           color="black",
           alpha = .7) +
  labs(x = "Zodiaco", 
       y = "Porcentaje", 
       title = "Individuos por zodiaco") +
  # Le decimos que y está en un scale de porcentaje
  scale_y_continuous(labels = scales::percent) 
## mutate: new variable 'total_base' (integer) with one unique value and 0% NA
## group_by: one grouping variable (sign)
## mutate (grouped): new variable 'total_signo' (integer) with 7 unique values and 0% NA
##                   new variable 'pct_signo' (double) with 7 unique values and 0% NA
## distinct (grouped): removed 86 rows (88%), 12 rows remaining

Muchas veces van a querer ordenar sus barras por frecuencia (o porcentaje). Para hacer esto con el gráfico anterior, podemos utilizar la función reorder y ordenar las categorías por frecuencia.

Marriage %>%
  mutate(total_base = n()) %>% # Calcula el total de observaciones de la base
  group_by(sign) %>%
  mutate(total_signo = n(), # Calcula el total de observaciones por zodiaco
         pct_signo = total_signo/total_base) %>% # Calcula el porcentaje
  # Como ahora mi unidad de analisis es el signo, entonces tenemos que cambiar la base de datos a esa unidad:
  distinct(sign, .keep_all=T) %>%
  # Ordenamos la variabel sign en función de pct_signo
  ggplot(aes(x = reorder(sign,pct_signo), y = pct_signo)) + 
  geom_col(fill = "cornflowerblue", 
           color="black",
           alpha = .7) +
  labs(x = "Zodiaco", 
       y = "Porcentaje", 
       title = "Individuos por zodiaco") +
  # Le decimos que y está en un scale de porcentaje
  scale_y_continuous(labels = scales::percent) 
## mutate: new variable 'total_base' (integer) with one unique value and 0% NA
## group_by: one grouping variable (sign)
## mutate (grouped): new variable 'total_signo' (integer) with 7 unique values and 0% NA
##                   new variable 'pct_signo' (double) with 7 unique values and 0% NA
## distinct (grouped): removed 86 rows (88%), 12 rows remaining

o, si quieren que descienda, le ponemos un - a la variable por la cual estamos ordenando:

Marriage %>%
  mutate(total_base = n()) %>% # Calcula el total de observaciones de la base
  group_by(sign) %>%
  mutate(total_signo = n(), # Calcula el total de observaciones por zodiaco
         pct_signo = total_signo/total_base) %>% # Calcula el porcentaje
  # Como ahora mi unidad de analisis es el signo, entonces tenemos que cambiar la base de datos a esa unidad:
  distinct(sign, .keep_all=T) %>%
  ggplot(aes(x = reorder(sign,-pct_signo), y = pct_signo)) + 
  geom_col(fill = "cornflowerblue", 
           color="black",
           alpha = .7) +
  labs(x = "Zodiaco", 
       y = "Porcentaje", 
       title = "Individuos por zodiaco") +
  # Le decimos que y está en un scale de porcentaje
  scale_y_continuous(labels = scales::percent) 
## mutate: new variable 'total_base' (integer) with one unique value and 0% NA
## group_by: one grouping variable (sign)
## mutate (grouped): new variable 'total_signo' (integer) with 7 unique values and 0% NA
##                   new variable 'pct_signo' (double) with 7 unique values and 0% NA
## distinct (grouped): removed 86 rows (88%), 12 rows remaining

Si quiren más información, podemos agregar el porcentaje de cada barra utilizando la función geom_text.

Marriage %>%
  # Calcula el total de observaciones de la base
  mutate(total_base = n()) %>% 
  group_by(sign) %>%
  # Calcula el total de observaciones por zodiaco
  mutate(total_signo = n(), 
         # Calcula el porcentaje
         pct_signo = total_signo/total_base, 
         # Le damos formato al label
         pct_label = paste0(round(pct_signo*100), "%")) %>% 
  # Como ahora mi unidad de analisis es el signo, entonces tenemos que cambiar la base de datos a esa unidad:
  distinct(sign, .keep_all=T) %>%
  ggplot(aes(x = reorder(sign,-pct_signo), y = pct_signo)) + 
  geom_col(fill = "cornflowerblue", 
           color="black",
           alpha = .7) +
  labs(x = "Zodiaco", 
       y = "Porcentaje", 
       title = "Individuos por zodiaco") +
  # Le decimos que y está en un scale de porcentaje
  scale_y_continuous(labels = scales::percent) +
  # Sacamos los labels de pct_signo. También podemos ajustar la altura del texto on relación a las barras (opcional)
  geom_text(aes(label = pct_label), 
            vjust = -0.25) 
## mutate: new variable 'total_base' (integer) with one unique value and 0% NA
## group_by: one grouping variable (sign)
## mutate (grouped): new variable 'total_signo' (integer) with 7 unique values and 0% NA
##                   new variable 'pct_signo' (double) with 7 unique values and 0% NA
##                   new variable 'pct_label' (character) with 7 unique values and 0% NA
## distinct (grouped): removed 86 rows (88%), 12 rows remaining

Ya casi. Pero esos nombres de las variables se sobreponen y están feísimos. Tenemos dos soluciones: 1) podemos rotar los ejes utilizando coord_flip o 2) podemos rotar los nombres. Veamos las dos soluciones:

Marriage %>%
  # Calcula el total de observaciones de la base
  mutate(total_base = n()) %>% 
  group_by(sign) %>%
  # Calcula el total de observaciones por zodiaco
  mutate(total_signo = n(), 
         # Calcula el porcentaje
         pct_signo = total_signo/total_base, 
         # Le damos formato al label
         pct_label = paste0(round(pct_signo*100), "%")) %>% 
  # Como ahora mi unidad de analisis es el signo, entonces tenemos que cambiar la base de datos a esa unidad:
  distinct(sign, .keep_all=T) %>%
  ggplot(aes(x = reorder(sign,-pct_signo), y = pct_signo)) + 
  geom_col(fill = "cornflowerblue", 
           color="black",
           alpha = .7) +
  labs(x = "Zodiaco", 
       y = "Porcentaje", 
       title = "Individuos por zodiaco") +
  # Le decimos que y está en un scale de porcentaje
  scale_y_continuous(labels = scales::percent) +
  # Sacamos los labels de pct_signo. También podemos ajustar la posición horizontal del texto on relación a las barras (opcional)
  geom_text(aes(label = pct_label), hjust = -.15) +
  # Rotamos los ejes
  coord_flip()
## mutate: new variable 'total_base' (integer) with one unique value and 0% NA
## group_by: one grouping variable (sign)
## mutate (grouped): new variable 'total_signo' (integer) with 7 unique values and 0% NA
##                   new variable 'pct_signo' (double) with 7 unique values and 0% NA
##                   new variable 'pct_label' (character) with 7 unique values and 0% NA
## distinct (grouped): removed 86 rows (88%), 12 rows remaining

Marriage %>%
  # Calcula el total de observaciones de la base
  mutate(total_base = n()) %>% 
  group_by(sign) %>%
  # Calcula el total de observaciones por zodiaco
  mutate(total_signo = n(), 
         # Calcula el porcentaje
         pct_signo = total_signo/total_base, 
         # Le damos formato al label
         pct_label = paste0(round(pct_signo*100), "%")) %>% 
  # Como ahora mi unidad de analisis es el signo, entonces tenemos que cambiar la base de datos a esa unidad:
  distinct(sign, .keep_all=T) %>%
  ggplot(aes(x = reorder(sign,-pct_signo), y = pct_signo)) + 
  geom_col(fill = "cornflowerblue", 
           color="black",
           alpha = .7) +
  labs(x = "Zodiaco", 
       y = "Porcentaje", 
       title = "Individuos por zodiaco") +
  # Le decimos que y está en un scale de porcentaje
  scale_y_continuous(labels = scales::percent) +
  # Sacamos los labels de pct_signo. También podemos ajustar la posición horizontal del texto on relación a las barras (opcional)
  geom_text(aes(label = pct_label), 
            vjust = -.25) +
  # Rotamos el texto el eje x
  theme(axis.text.x = element_text(angle = 90, 
                                   hjust = 1))
## mutate: new variable 'total_base' (integer) with one unique value and 0% NA
## group_by: one grouping variable (sign)
## mutate (grouped): new variable 'total_signo' (integer) with 7 unique values and 0% NA
##                   new variable 'pct_signo' (double) with 7 unique values and 0% NA
##                   new variable 'pct_label' (character) with 7 unique values and 0% NA
## distinct (grouped): removed 86 rows (88%), 12 rows remaining

Poniendo junto todo lo que aprendimos antes podemos hacer un gráfico que se ve algo así:

Marriage %>%
  # Calcula el total de observaciones de la base
  mutate(total_base = n()) %>% 
  group_by(sign) %>%
  # Calcula el total de observaciones por zodiaco
  mutate(total_signo = n(), 
         # Calcula el porcentaje
         pct_signo = total_signo/total_base, 
         # Le damos formato al label
         pct_label = paste0(round(pct_signo*100), "%")) %>% 
  # Como ahora mi unidad de analisis es el signo, entonces tenemos que cambiar la base de datos a esa unidad:
  distinct(sign, .keep_all=T) %>%
  ggplot(aes(x = reorder(sign,-pct_signo), y = pct_signo)) + 
  geom_col(fill = "cornflowerblue", 
           color="black",
           alpha = .7) +
  # Le decimos que y está en un scale de porcentaje
  scale_y_continuous(breaks = seq(0, .2, .05),
                     labels = scales::percent,
                     limits = c(0,.2)) +
  # Sacamos los labels de pct_signo. También podemos ajustar la posición horizontal del texto on relación a las barras (opcional)
  geom_text(aes(label = pct_label), 
            vjust = -.25) +
  theme_minimal() + 
  # Rotamos el texto el eje x
  theme(axis.text.x = element_text(angle = 90, 
                                   hjust = 1)) +
  labs(title = "Porcentaje de Matrimonios por Zodiaco",
       subtitle = "Registro del estado de Alabama",
       # caption = "fuente: http://mosaic-web.org/",
       x = "", 
       y = "Porcentaje") 
## mutate: new variable 'total_base' (integer) with one unique value and 0% NA
## group_by: one grouping variable (sign)
## mutate (grouped): new variable 'total_signo' (integer) with 7 unique values and 0% NA
##                   new variable 'pct_signo' (double) with 7 unique values and 0% NA
##                   new variable 'pct_label' (character) with 7 unique values and 0% NA
## distinct (grouped): removed 86 rows (88%), 12 rows remaining

Variables Continuas

La distribución de una sola variable continua por lo general se la grafica en un histograma o una gráfico de densidad.

Histogramas

Utilizando la misma base de datos del ejemplo anterior, vamos a graficar el histograma de la edad de los participantes.

Marriage %>%
  ggplot(aes(x = age)) + 
  geom_histogram() + 
  labs(title = "Participantes por edad",
       x = "Edad")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

La mayoría de los participantes parecen estar en sus veintes y otro grupo en sus cuarenta con uno cuantos rucos rucos al final. Esto es una distribución multimodal.

Podemos modificar los colores utilizanto fill y color (para el borde).

Marriage %>%
  ggplot(aes(x = age)) + 
  geom_histogram(fill = "peachpuff", 
           color="peru",
           alpha = .7) + 
  labs(title = "Participantes por edad",
       x = "Edad")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Una de las opciones más importantes de los histogramas son los bins que controla el número de contenedores en las cuales la variable númerica está dividida (i.e., el número de barras en el gráfico). El default es 30, pero es bueno intentar con números más grandes o más pequeños para tener una mejor representación de la forma de la distribución.

Marriage %>%
  ggplot(aes(x = age)) + 
  geom_histogram(fill = "peachpuff", 
           color="peru",
           alpha = .7,
           bins = 20) + 
  labs(title = "Participantes por edad",
       subtitle = "bins = 20",
       x = "Edad")

También pueden especificar el binwidth, la ancho de los contenedores representados por las barras.

Marriage %>%
  ggplot(aes(x = age)) + 
  geom_histogram(fill = "peachpuff", 
           color="peru",
           alpha = .7,
           binwidth = 3) + 
  labs(title = "Participantes por edad",
       subtitle = "binwidth = 3",
       x = "Edad")

Density Plot

Una manera alternativa a un histograma es un kernel density plot. La estimación del kernel density es un método no paramétrico para estimar la función de probabilidad de la densidad de una variable continua aleatoria. En fácil, estamos intentando graficar un histograma continuo donde el área bajo la curva es igual a uno.

Marriage %>%
  ggplot(aes(x = age)) + 
  geom_density() + 
  labs(title = "Participantes por edad")

Al igual que con los histogramas, podemos cambiar el color y el fill. También podemos modificar el bw o el término que determina cuánto de la data toma a la vez para hacer las curvas.

Marriage %>%
  ggplot(aes(x = age)) + 
  geom_density(fill = "peachpuff", 
           color="peru",
           alpha = .7,
           bw = 2) + 
  labs(title = "Participantes por edad",
       subtitle = "bandwidth = 2",
       x = "Edad") +
  theme_minimal()

Gráficos Bivariados

Los gráficos bivariados muestran la relación entre dos variables. El tipo de gráfico dependerá del tipo de variables (categóricas o continuas).

Categórica vs. Categórica

Lo más común para representar la relación entre dos variables categóricas es en gráficos de barras. Por ejemplo, podemos evaluar la relación entre el género y la posición académica en una base de datos que describe los salarios de 397 profesores universitario (2009).

Veamos un stacked bar plot:

data(Salaries, package="carData")

Salaries %>%
  ggplot(aes(x = rank, fill = sex)) + 
  geom_bar() 

Un poco difícil de ver. Podemos ponerlas una junto a la otra.

Salaries %>%
  ggplot(aes(x = rank, fill = sex)) + 
  geom_bar(position = "dodge") 

También podemos hacer que cada barra represente el 100%.

Salaries %>%
  ggplot(aes(x = rank, fill = sex)) + 
  geom_bar(position = "fill") +
  labs(y = "Proporción")

Este último gráfico es particularmente útil si su objetivo es comparar el porcentaje de una categoría en función de otra variable. Por ejemplo. la proporción de mujeres se reduce a medida que aumenta la posición de Assistant Professor, a Associate Professor, a Professor.

Como en todo, podemos personalizar y embellecer los gráficos.

Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  group_by(rank) %>%
  mutate(total_rank= n()) %>%
  group_by(rank,sex) %>%
  mutate(total_rank_sex = n(),
         pct_rank_sex = total_rank_sex/total_rank,
         lbl = scales::percent(pct_rank_sex)) %>%
  ungroup() %>%
  ggplot(aes(x = rank,fill = sex)) + 
  geom_bar(position = "fill") +
  scale_y_continuous(breaks = seq(0, 1, .2), 
                     labels = scales::percent) +
  scale_fill_viridis_d(option="E",alpha = 0.5) +
  labs(x = "Posición", 
       fill = "Género",
       title = "Posición por Género",
       y = "") +
  theme_minimal()
## mutate: changed 397 values (100%) of 'rank' (0 new NA)
## group_by: one grouping variable (rank)
## mutate (grouped): new variable 'total_rank' (integer) with 3 unique values and 0% NA
## group_by: 2 grouping variables (rank, sex)
## mutate (grouped): new variable 'total_rank_sex' (integer) with 6 unique values and 0% NA
##                   new variable 'pct_rank_sex' (double) with 6 unique values and 0% NA
##                   new variable 'lbl' (character) with 4 unique values and 0% NA
## ungroup: no grouping variables

Continuas vs. Continuas

La relación entre dos variables continuas por lo general es representada por un scatter plot o por una gráfico de línea. Comencemos por el primero.

Scatter Plot

El scatter plot es un gráfico donde se representa cada vairable en un eje. Por ejemplo, podemos utilizar la misma base de datos anterior y graficar experiencia (yrs.since.phd) vs. salario académico (salary) para profesores universitarios.

# scatterplot simple
Salaries %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary)) +
  geom_point()

las optiones de geom_point pueden ser cambiadas:

  • color: color del punto
  • size: tamaño del punto
  • shape: forma del punto
  • alpha : transparencia del punto

Como aprendimos antes, las funciones scale_x_continuous y scale_y_continuous controlan las escalas del eje x y el eje y.

Como siempre, usamos estas funciones para hacer gráficos más atractivos.

# scatterplot simple
Salaries %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary)) +
  geom_point(color="peachpuff", 
             size = 2, 
             alpha=.7) +
  scale_y_continuous(label = scales::dollar, 
                     limits = c(50000, 250000)) +
  scale_x_continuous(breaks = seq(0, 60, 10), 
                     limits=c(0, 60)) + 
  labs(x = "Años desde el PhD",
       y = "",
       title = "Experiencia vs. Salario",
       subtitle = "Salario de 9 meses entre 2008 y 2009") +
  theme_minimal()

Podemos resumir la relación que se muestra en el scatterplot utilizando un best fit line. Hay varias maneras de hacer la línea (de representar la relación): lineal, polinomial, o no paramétrica (loess). Por default, el intervalo de confianza mostrado es del 95%.

# scatterplot lineal
Salaries %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary)) +
  geom_point(color="peachpuff", 
             size = 2, 
             alpha=.7) +
  geom_smooth(method = "lm",
              color = "peru") +
  scale_y_continuous(label = scales::dollar, 
                     limits = c(50000, 250000)) +
  scale_x_continuous(breaks = seq(0, 60, 10), 
                     limits=c(0, 60)) + 
  labs(x = "Años desde el PhD",
       y = "",
       title = "Experiencia vs. Salario",
       subtitle = "Salario de 9 meses entre 2008 y 2009") +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

Claramente, a medida que aumenta la experiencia también aumenta el salario. Sin embargo, parece que hay un caída justo al final. Los profesores con mucha experiencia tienen menores salarios. Una línea recta no captura este efecto no-lineal. Una curva captura mejor este efecto.

Un polinomio dibuja una curva con la siguiente forma:

\[\hat{y} = \beta_0 + \beta_1x + \beta_2x^2 + \beta_3x^3+...\]

Por lo general, una fórmula cuadrática (una curva) o cúbica (dos curvas) se utiliza. Rara vez se utiliza una curva de un polinomio de orden mayor a 3. Utilizando una fórmula cuadrática obtenemos el siguiente gráfico:

# scatterplot cuadrático
Salaries %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary)) +
  geom_point(color="peachpuff", 
             size = 2, 
             alpha=.7) +
  geom_smooth(method = "lm",
              formula = y ~ poly(x, 2), 
              color = "peru") +
  scale_y_continuous(label = scales::dollar, 
                     limits = c(50000, 250000)) +
  scale_x_continuous(breaks = seq(0, 60, 10), 
                     limits=c(0, 60)) + 
  labs(x = "Años desde el PhD",
       y = "",
       title = "Experiencia vs. Salario",
       subtitle = "Salario de 9 meses entre 2008 y 2009") +
  theme_minimal()

Gráfico de Línea

Cuando una de las dos variables representa tiempo, un gráfico de línea puede ser una manera efectiva de mostrar la relación. Por ejemplo, el código a continuación muestra la relación entre tiempo (year) y el promedio de expectativa de vida (lifeExp_mean) en las Amerícas entre 1952 y 2007. La data viene de la base de datos gapminder.

data(gapminder, package="gapminder")
  
# obtenemos el promedio de vida en las Américas
americas <- gapminder %>%
  group_by(continent, year) %>%
  mutate(lifeExp_mean = mean(lifeExp)) %>%
  filter(continent == "Americas") %>%
  distinct(continent, year, .keep_all=T) %>%
  ungroup()
## group_by: 2 grouping variables (continent, year)
## mutate (grouped): new variable 'lifeExp_mean' (double) with 60 unique values and 0% NA
## filter (grouped): removed 1,404 rows (82%), 300 rows remaining
## distinct (grouped): removed 288 rows (96%), 12 rows remaining
## ungroup: no grouping variables
# simple grafico de línea
americas %>%
  ggplot(aes(x = year, 
           y = lifeExp_mean)) +
  geom_point(color="peachpuff", 
             size = 3, 
             alpha=.8) +
  geom_line(color="peru", 
             size = 1, 
             alpha=.6) +
  labs(x = "Año",
       y = "Expectativa de Vida",
       title = "Cambios en la Expectativa de Vida a lo Largo del Tiempo",
       subtitle = "Las Américas (1952-2007)") +
  theme_minimal()

Continuas vs. Categórica

Cuando queremos graficar la relación entre una variable categórica y una continua, hay varios tipos de gráficos disponibles. Entre estos están los gráficos de barras, los density plots, box plots, etc. Veamos algunas opciones.

Gráficos de Barras 2

En la sección de Gráficos de Barras los utilizamos para mostrar el número de casos de una variable o de dos variables. También se pueden utilizar gráficos de barras para mostrar estadísticas básicas (por ej., promedio o mediana) de una variable continua para cada nivel de una variable categórica.

Por ejemplo, el siguiente gráfico muestra el salario promedio de profesores universitarios en cada posición académica.

library(scales) # para poder utilizar dollar()
## 
## Attaching package: 'scales'
## The following object is masked from 'package:purrr':
## 
##     discard
## The following object is masked from 'package:readr':
## 
##     col_factor
Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
    # calcular promedios
  group_by(rank) %>%
  mutate(mean_salary = mean(salary)) %>%
  distinct(rank, .keep_all=T) %>%
  # graficar
  ggplot(aes(x = rank, 
           y = mean_salary)) +
  geom_bar(stat = "identity", 
           fill = "peachpuff",
           color = "peru",
           alpha = .75) +
  geom_text(aes(label = dollar(mean_salary)), 
            vjust = -0.3) +
  scale_y_continuous(breaks = seq(0, 130000, 20000), 
                     label = dollar) +
  labs(title = "Salario Promedio por Posicion", 
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       x = "",
       y = "") +
  theme_minimal()
## mutate: changed 397 values (100%) of 'rank' (0 new NA)
## group_by: one grouping variable (rank)
## mutate (grouped): new variable 'mean_salary' (double) with 3 unique values and 0% NA
## distinct (grouped): removed 394 rows (99%), 3 rows remaining

Un problema con este tipo de gráficos es que no muestran la distribucion de la data. El siguiente gráfico trata de corregir esa limitación.

Gráficos de Densidad de Grupos

Hay varias maneras de utilizar los gráficos de densidad para comparar distribuciones. Aquí hay tres.

  1. Distribuciones sobreimpuestas:
Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  # calcular promedios
  group_by(rank) %>%
  mutate(mean_salary = mean(salary)) %>%
  # graficar
  ggplot(aes(x = salary,
             fill = rank,
             color = rank)) +
  geom_density() + 
  # Dibujar las líneas verticales
  geom_vline(aes(xintercept = mean_salary, color = rank), linetype="dashed") +
  scale_fill_viridis_d(option="E",alpha = 0.5) +
  scale_color_viridis_d(option="E",alpha = 0.85) +
    labs(title = "Distribución de Salario por Posición", 
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       x = "Salario",
       y = "" ,
       fill = "Posición",
       color = "Posición") +
  theme_minimal()
## mutate: changed 397 values (100%) of 'rank' (0 new NA)
## group_by: one grouping variable (rank)
## mutate (grouped): new variable 'mean_salary' (double) with 3 unique values and 0% NA

  1. Distribuciones en facets:
Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  # calcular promedios
  group_by(rank) %>%
  mutate(mean_salary = mean(salary)) %>%
  # graficar
  ggplot(aes(x = salary,
             fill = rank,
             color = rank)) +
  geom_density() + 
  # Dibujar las líneas verticales
  geom_vline(aes(xintercept = mean_salary, color = rank), linetype="dashed") +
  # Facets
  facet_wrap(~rank, ncol = 1) +
  scale_fill_viridis_d(option="E",alpha = 0.5) +
  scale_color_viridis_d(option="E",alpha = 0.85) +
    labs(title = "Distribución de Salario por Posición", 
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       x = "Salario",
       y = "" ) +
  theme_minimal() +
  theme(legend.position = "none")
## mutate: changed 397 values (100%) of 'rank' (0 new NA)
## group_by: one grouping variable (rank)
## mutate (grouped): new variable 'mean_salary' (double) with 3 unique values and 0% NA

  1. Distribuciones sobreimpuestas y en facets:
library(ggridges)

Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  # calcular promedios
  group_by(rank) %>%
  mutate(mean_salary = mean(salary)) %>%
  # graficar
  ggplot(aes(x = salary,
             y = rank,
             fill = rank,
             color = rank)) +
  geom_density_ridges() + 
  # Dibujar las líneas verticales
  geom_vline(aes(xintercept = mean_salary, color = rank), linetype="dashed") +
  # Facets
  scale_fill_viridis_d(option="E",alpha = 0.5) +
  scale_color_viridis_d(option="E",alpha = 0.85) +
    labs(title = "Distribución de Salario por Posición", 
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       x = "Salario",
       y = "" ) +
  theme_minimal() +
  theme(legend.position = "none")
## mutate: changed 397 values (100%) of 'rank' (0 new NA)
## group_by: one grouping variable (rank)
## mutate (grouped): new variable 'mean_salary' (double) with 3 unique values and 0% NA
## Picking joint bandwidth of 5590

Box Plots

Un box plot muestra el percentil 25to, la media y el percentil 75to de uns distribución. Los bigotes (whiskers o líneas verticales) caputuran el 99% de una distribución normal, y la observaciones que están fuera de ese rango se grafica como puntos que representan outliers (ver la Figura de abajo).

Ejemplo de box plot
Ejemplo de box plot

Si ponemos dos box plot uno al lado del otro, estos resultan muy útiles para comparar grupos en una variabla continua.

Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  # graficar
  ggplot(aes(y = salary,
             x = rank,
             fill = rank,
             color = rank)) +
  # dibujar boxplots 
  geom_boxplot() + 
  scale_y_continuous(breaks = seq(0, 300000, 50000), 
                     label = dollar) +
  scale_fill_viridis_d(option="E",alpha = 0.5) +
  scale_color_viridis_d(option="E",alpha = 0.85) +
    labs(title = "Distribución de Salario por Posición", 
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       x = "Posición",
       y = "" ) +
  theme_minimal() +
  theme(legend.position = "none")
## mutate: changed 397 values (100%) of 'rank' (0 new NA)

Una alternativa que combina el box plot con el density distribution es el violin plot (es un plot de densidad rotado 90 grados y duplicado como espejo).

Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  # graficar
  ggplot(aes(y = salary,
             x = rank,
             fill = rank,
             color = rank)) +
  # dibujar boxplots 
  geom_violin() + 
  scale_y_continuous(breaks = seq(0, 300000, 50000), 
                     label = dollar) +
  scale_fill_viridis_d(option="E",alpha = 0.5) +
  scale_color_viridis_d(option="E",alpha = 0.85) +
    labs(title = "Distribución de Salario por Posición", 
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       x = "Posición",
       y = "" ) +
  theme_minimal() +
  theme(legend.position = "none")
## mutate: changed 397 values (100%) of 'rank' (0 new NA)

O, mejor aún, pueden combinar el box plot con el violin plot:

Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  # graficar
  ggplot(aes(y = salary,
             x = rank,
             fill = rank,
             color = rank)) +
  # dibujar boxplots 
  geom_violin() + 
  geom_boxplot(width = .15, 
               outlier.size = 2) +
  scale_y_continuous(breaks = seq(0, 300000, 50000), 
                     label = dollar) +
  scale_fill_viridis_d(option="E",alpha = 0.5) +
  scale_color_viridis_d(option="E",alpha = 0.85) +
    labs(title = "Distribución de Salario por Posición", 
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       x = "Posición",
       y = "" ) +
  theme_minimal() +
  theme(legend.position = "none")
## mutate: changed 397 values (100%) of 'rank' (0 new NA)

Gráficos Multivariados

Para graficar la relación entre tres o más variables (aunque ya tres es mucho), podemos utilizar dos herramientas que ya usamos previamente: grouping y faceting.

Grouping

Podemos retomar el scatter plot que hicimos antes y agregarle una categoría más:

# scatterplot por categoria
Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary,
           color = rank)) +
  geom_point(size = 2) +
  scale_color_viridis_d(option="E",alpha = 0.85) +
  scale_y_continuous(label = scales::dollar, 
                     limits = c(50000, 250000)) +
  scale_x_continuous(breaks = seq(0, 60, 10), 
                     limits=c(0, 60)) + 
  labs(x = "Años desde el PhD",
       y = "",
       title = "Experiencia vs. Salario",
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       color = "Posición") +
  theme_minimal()
## mutate: changed 397 values (100%) of 'rank' (0 new NA)

Ahora, podemos incluso agregar una dimensión más (género) modificando la forma de cada punto:

# scatterplot por categoria
Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary,
           color = rank,
           shape = sex)) +
  geom_point(size = 2) +
  scale_color_viridis_d(option="E",alpha = 0.85) +
  scale_y_continuous(label = scales::dollar, 
                     limits = c(50000, 250000)) +
  scale_x_continuous(breaks = seq(0, 60, 10), 
                     limits=c(0, 60)) + 
  labs(x = "Años desde el PhD",
       y = "",
       title = "Experiencia vs. Salario",
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       color = "Posición",
       shape = "Género") +
  theme_minimal()
## mutate: changed 397 values (100%) of 'rank' (0 new NA)

O podemos usar otra dimensión para resaltar el eje x o el eje y. Por ejemplo, podemos resaltar el salario:

# scatterplot por categoria resaltando salario
Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary,
           color = rank,
           size = salary)) +
  geom_point() +
  scale_color_viridis_d(option="E",alpha = 0.65) +
  scale_y_continuous(label = scales::dollar, 
                     limits = c(50000, 250000)) +
  scale_x_continuous(breaks = seq(0, 60, 10), 
                     limits=c(0, 60)) + 
  labs(x = "Años desde el PhD",
       y = "",
       title = "Experiencia vs. Salario",
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       color = "Posición",
       size = "Salario") +
  theme_minimal()
## mutate: changed 397 values (100%) of 'rank' (0 new NA)

Y resaltando el eje x (experiencia) se vería así:

# scatterplot por categoria resaltando salario
Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary,
           color = rank,
           size = yrs.since.phd)) +
  geom_point() +
  scale_color_viridis_d(option="E",alpha = 0.5) +
  scale_y_continuous(label = scales::dollar, 
                     limits = c(50000, 250000)) +
  scale_x_continuous(breaks = seq(0, 60, 10), 
                     limits=c(0, 60)) + 
  labs(x = "Años desde el PhD",
       y = "",
       title = "Experiencia vs. Salario",
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       color = "Posición",
       size = "Experiencia") +
  theme_minimal()
## mutate: changed 397 values (100%) of 'rank' (0 new NA)

Finalmente, podemos hacer las misma best fit lines, pero esta vez por categoría.

# scatterplot por categoria
Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary,
           color = rank,
           fill = rank)) +
  geom_point(size = 2) +
  geom_smooth(method = "lm",
              formula = y ~ poly(x, 2)) +
  scale_color_viridis_d(option="E",alpha = 0.5) +
  scale_fill_viridis_d(option="E",alpha = 0.65) +
  scale_y_continuous(label = scales::dollar, 
                     limits = c(50000, 250000)) +
  scale_x_continuous(breaks = seq(0, 60, 10), 
                     limits=c(0, 60)) + 
  labs(x = "Años desde el PhD",
       y = "",
       title = "Experiencia vs. Salario",
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       color = "Posición",
       fill = "Posición") +
  theme_minimal()
## mutate: changed 397 values (100%) of 'rank' (0 new NA)

Faceting

Mientras grouping te permite visualizar todas las dimensiones y categorías en un solo gráfico, con faceting puedes dividir en subgráficos tu data. Cada subgráfico estará limitado a la categoría que elijas. Por ejemplo, podemos replicar el gráfico anterior, pero en lugar de hacer grouping por posición, podemos hacer faceting por posición.

# scatterplot por categoria
Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary,
           # Mantengo esto por estética
           color = rank,
           fill = rank)) +
  geom_point(size = 2) +
  geom_smooth(method = "lm",
              formula = y ~ poly(x, 2)) +
  scale_color_viridis_d(option="E",alpha = 0.5) +
  scale_fill_viridis_d(option="E",alpha = 0.65) +
  scale_y_continuous(label = scales::dollar, 
                     limits = c(50000, 250000)) +
  scale_x_continuous(breaks = seq(0, 60, 10), 
                     limits=c(0, 60)) + 
  facet_wrap(~ rank, ncol = 1) +
  labs(x = "Años desde el PhD",
       y = "",
       title = "Experiencia vs. Salario",
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       color = "Posición",
       fill = "Posición") +
  theme_minimal() +
  theme(legend.position = "none")
## mutate: changed 397 values (100%) of 'rank' (0 new NA)

Podemos agregar facets por más de una categoría. El gráfico anterior le podemos agregar un facet de género también.

# scatterplot por categoria
Salaries %>%
  mutate(rank = recode_factor(rank,
                       AsstProf = "Assistant Professor", 
                       AssocProf = "Associate Professor",
                       Prof = "Professor")) %>%
  ggplot(aes(x = yrs.since.phd, 
           y = salary,
           # Mantengo esto por estética
           color = rank,
           fill = rank)) +
  geom_point(size = 2) +
  # Cambiamos a lineal porque hay menos data por facet
  geom_smooth(method = "lm",
              formula = y ~ poly(x, 1)) +
  scale_color_viridis_d(option="E",alpha = 0.5) +
  scale_fill_viridis_d(option="E",alpha = 0.65) +
  scale_y_continuous(label = scales::dollar) +
  # Agrego scales = "free" para que se muevan libremente los ejes.
  facet_wrap(sex ~ rank, scales = "free") +
  labs(x = "Años desde el PhD",
       y = "",
       title = "Experiencia vs. Salario",
       subtitle = "Salario de 9 meses entre 2008 y 2009",
       color = "Posición",
       fill = "Posición") +
  theme_minimal() +
  theme(legend.position = "none")
## mutate: changed 397 values (100%) of 'rank' (0 new NA)

Finalmente, vamos a regrear a nuestra línea de tiempo y, en lugar de hacer un solo gráfico de la expectativa de vida para todo el continente, podemos hacer uno por país, utilizando facets:

gapminder %>%
  filter(continent == "Americas") %>%
  ggplot(aes(x=year, y = lifeExp)) +
  geom_line(color="peachpuff") +
  geom_point(color="peru") +
  facet_wrap(~country) + 
  theme_minimal(base_size = 9) +
  theme(axis.text.x = element_text(angle = 45, 
                                   hjust = 1)) +
  labs(title = "Evolución de la Expectativa de Vida",
       x = "Año",
       y = "Expectativa de Vida") 
## filter: removed 1,404 rows (82%), 300 rows remaining

Podemos observar como en todo los países la expectativa de vida sube, salvo en El Salvador entre 1970 y 1990.

Conclusión:

¿Hay más gráficos que se pueden hacer? Los hay. Pero la única manera de saber qué gráficos necesita su historia es trabajando y conociendo su data. Recuerden que la visualización de la data tiene que comunicar un punto de manera efectiva. Demasiada información, y el lector no sabrá qué priorizar en el gráfico. Muy poco información, y el lector no sabrá sobre qué es el gráfico. El barroco es enemigo de la visualización de datos:

El barroco es enemigo de la visualización de datos
El barroco es enemigo de la visualización de datos

Finalmente, cualquier pregunta que puedan tener acerca de la visualización de datos, seguramente pueden encontrar la respuesta en el internet. Ahora sí, vayan al mundo y grafiquen!!

Un buen gráfico que ilustra su punto
Un buen gráfico que ilustra su punto
LS0tCnRpdGxlOiAiVGFsbGVyIGRlIFZpc3VhbGl6YWNpw7NuIGRlIERhdG9zIgphdXRob3I6ICJTZWJhc3Rpw6FuIFZhbGxlam8gVmVyYSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGhpZ2hsaWdodDogInRhbmdvIgotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHlsb2cpCmBgYAoKIyBJbnRyb2R1Y2Npw7NuOiBUYWxsZXIgZGUgVmlzdWFsaXphY2nDs24gZGUgRGF0b3MKCkVuIGVzdGUgY29ydG8gdGFsbGVyIHZhbW9zIGEgYXByZW5kZXIgbGFzIGhlcnJhbWllbnRhcyBiw6FzaWNhcyBwYXJhIHZpc3VhbGl6YXIgc3UgZGF0YS4gTGEgdmlzdWFsaXphY2nDs24gZGUgZGF0b3MsIGFtcGxpYW1lbnRlIGVudGVuZGlkYSwgZXMgdW5hIG1hbmVyYSAoZWZlY3RpdmEsIGN1YW5kbyBzZSBsYSBoYWNlIGJpZW4pIGRlIGNvbnRhciB1bmEgaGlzdG9yaWEgKGxhIGhpc3RvcmlhIHF1ZSB0w7ogcXVpZXJlcyBjb250YXIpLiBVbmEgbWFsYSB2aXN1YWxpemFjacOzbiBkZSBkYXRvcyBlcyBjb21vIHVuYSBtYWxhIG5hcnJhdGl2YS4gVW5hIGJ1ZW5hIHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIGVzICoqY2hlZnMga2lzcyoqLiAKCkEgbWFuZXJhIHJlc3VtaWRhLCBoYXkgZG9zIHRpcG9zIGRlIHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zOiAKCi0gKipWaXN1YWxpemFjaW9uZXMgZXhwbG9yYXRpdmFzKio6IHZpc3VhbGl6YWNpb25lcyBxdWUgbm9zIGF5dWRhbiBhIGVudGVuZGVyIG51ZXN0cm9zIGRhdG9zLiAKLSAqKlZpc3VhbGl6YWNpb25lcyBleHBsaWNhdGl2YXMqKjogdmlzdWFsaXphY2lvbmVzIHF1ZSBub3MgYXl1ZGFuIGEgY29tcGFydGlyIG51ZXN0cm9zIGhhbGxhemdvcyBjb24gb3Ryb3MuCgpUb21hbmRvIGVuIGN1ZW50YSBlc3RhcyBkb3MgY2F0ZWdvcsOtYXMsIGVzIGltcG9ydGFudGUgbWFudGVuZXIgZW4gYWxnw7puIGxhZG8gZGUgc3UgcHNpcXVpcyBsYXMgc2lndWllbnRlcyBjYXJhY3RlcsOtc3RpY2FzIGN1YW5kbyBlc3TDqW4gZGVzYXJyb2xsYW5kbyBzdXMgZ3LDoWZpY29zOgoKMS4gKipWaXN1YWxpemFjaW9uZXMgZXhwbG9yYXRpdmFzKioKICAgIGEuIE1hbnRlbiBsYSBtYXlvciBjYW50aWRhZCBkZSBkZXRhbGxlIHBvc2libGUKICAgIGIuIExpbWl0YSBlbCBncsOhZmljbyBhIGxvIHF1ZSAqdMO6KiBwdWVkZXMgdmVyIGUgaW50ZXJwcmV0YXIKMi4gKipWaXN1YWxpemFjaW9uZXMgZXhwbGljYXRpdmFzKioKICAgIGEuIFJlcXVpZXJlIGRlIGRlY2lzaW9uZXMgZWRpdG9yaWFsZXMgKHR1eWFzKQogICAgYi4gUmVzYWx0YSBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBxdWUgc2lydmFuIHBhcmEgY29udGFyIHR1IGhpc3RvcmlhCiAgICBjLiBFbGltaW5hIGRldGFsbGVzIHN1cGVyZmx1b3MKCkVuIGVsIHJlc3RvIGRlIGVzdGUgdGFsbGVyLCB2YW1vcyBhIHV0aWxpemFyIFIgcGFyYSBxdWUgZGUgdHVzIG7Dum1lcm9zIHB1ZWRhcyBzYWNhciB1bmEgaGlzdG9yaWEgKG8gbG9zIHByaW5jaXBpb3MgZGUgdW5hIGhpc3RvcmlhKS4gRXN0ZSB0YWxsZXIgKipubyB2YSBhIGN1YnJpcioqIHRlb3LDrWFzIHNvYnJlIHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zLiBQYXJhIGVzbyBuZWNlc2l0YXLDrWFtb3MgYmFzdGFudGUgbcOhcyB0aWVtcG8uIFNpbiBlbWJhcmdvLCBzw60gcmVjb21pZW5kbyBxdWUgbGVhbiBhbCByZXNwZWN0by4gQXVucXVlIHNlIHF1ZSBpZ3VhbCBubyBsbyB2YW4gYSBoYWNlciwgdW5hIGJ1ZW5hIGludHJvZHVjY2nDs24gYWwgdGVtYSBlcyBbV2lsZGUsIENsYXVzZSAoMjAxOSkgLSBGdW5kYW1lbnRhbHMgb2YgRGF0YSBWaXN1YWxpemF0aW9uXShodHRwczovL3d3dy5vcmVpbGx5LmNvbS9saWJyYXJ5L3ZpZXcvZnVuZGFtZW50YWxzLW9mLWRhdGEvOTc4MTQ5MjAzMTA3OS8pLgoKIyBQYXJ0ZSAxOiBTdSBEYXRhLCBgdGlkeXZlcnNlYCwgeSBgZ2dwbG90MmAKCkVuIGxhIHByaW1lcmEgcGFydGUgdmFtb3MgYSB2ZXIgZGlmZXJlbnRlcyBmb3JtYXRvcyBkZSBkYXRhLCBjw7NtbyBjYXJnYXJsb3MgYSBgUmAsIGVudGVuZGVyIGxhICpmb3JtYSogZGUgc3VzIGJhc2VzIGRlIGRhdG9zLCBlbnRlbmRlciBsYSAqZm9ybWEqIHF1ZSBgZ2dwbG90MmAgcHJlZmllcmUgZW4gcXVlIGVzdMOpbiBzdXMgZGF0b3MsIGPDs21vIGNyZWFyIHZhcmlhYmxlcywgY8OzbW8gbW9kaWZpY2FyIHN1cyBiYXNlcyBkZSBkYXRvcywgeSwgZmluYWxtZW50ZSwgbGEgaW50dWljacOzbiBiw6FzaWNhIHBhcmEgY3JlYXIgZ3LDoWZpY29zIGVuIGBSYC4gCgpQYXJhIGVzdGUgdGFsbGVyIHZhbW9zIGEgdXRpbGl6YXIsIHByaW5jaXBhbG1lbnRlLCBkb3MgcGFxdWV0ZXMgZGUgYFJgIGNvbiBsb3MgY3VhbGVzIGRlYmVyw61hbiBlc3RhciBmYW1pbGlhcml6YWR4czogYHRpZHl2ZXJzZWAgeSBgZ2dwbG90MmAuIGB0aWR5dmVyc2VgIGVzIHVuIHBhcXVldGUgcXVlIG5vcyBheXVkYSBhICptYW5pcHVsYXIqIG51ZXN0cm9zIGRhdG9zIChwb3IgZWouLCBlbGltaW5hciB2YXJpYWJsZXMsIGFncmVnYXIgb2JzZXJ2YWNpb25lcywgdW5pciBiYXNlcyBkZSBkYXRvcykuIFRpZW5lIHVuIGxlbmd1YWplIHBhcnRpY3VsYXI6ICpwaXBlcyouIENvbW8gZXN0ZSBubyBlcyB1biB0YWxsZXIgZGUgYHRpZHl2ZXJzZWAgeSB1c3RlZGVzIHNvbiBlc3R1ZGlhbnRlcyBkZWwgVGVjIHkgcGFnYW4gdW5hIG1pbGxvbmFkYSBwb3Igc3UgZWR1Y2FjacOzbiwgdm95IGEgYXN1bWlyIHF1ZSB0b2R4cyB0aWVuZW4gdW4gY29ub2NpbWllbnRvIGLDoXNpY28gZGUgYHRpZHl2ZXJzZWAuIFNpIHF1aWVyZW4gcmVwYXNhciBjw7NtbyBmdW5jaW9uYSBlc3RlIHBhcXVldGUsIHB1ZWRlbiByZXZpc2FyIGVsIHNpZ3VpZW50ZSBbZW5sYWNlXShodHRwczovL2Jvb2tkb3duLm9yZy95aWhfaHV5bmgvR3VpZGUtdG8tUi1Cb29rL3RpZHl2ZXJzZS5odG1sKS4gYGdncGxvdDJgIGVzdMOhIGluY29ycG9yYWRvIGEgYHRpZHl2ZXJzZWAsIHkgZXMgdW5hIGhlcnJhbWllbnRhIGJhc3RhbnRlIGRlc2Fycm9sbGFkYSwgaW50dWl0aXZhIChjdWFuZG8gbGUgYWdhcnJhcyBsYSB2dWVsdGEpLCB5IGNvbiB1bmEgZ3JhbcOhdGljYSBjb25zaXN0ZW50ZS4gRW4gZXN0ZSB0YWxsZXIgdmFtb3MgYSBhZGVudHJhcm5vcyBlbiBsYSBsw7NnaWNhIGRlIGBnZ3Bsb3QyYC4gU2luIGVtYmFyZ28sIG5vIGxvZ3JhcmVtb3MgY3VicmlyIGFic29sdXRhbWVudGUgdG9kbyBsbyBxdWUgc2UgcHVlZGUgaGFjZXIgY29uIGBnZ3Bsb3QyYCBlbiBlc3BlY8OtZmljbywgbmkgY29uIGBSYCBlbiBnZW5lcmFsLiBQYXJhIGxlZXIgbcOhcyBzb2JyZSBsYSBncmFtw6F0aWNhIGRlIGBnZ3Bsb3RgLCByZWNvbWllbmRvIHF1ZSBsZWFuIFtXaWNraGFtLCBIYWRseWUgKDIwMTApIC0gQSBMYXllcmVkIEdyYW1tYXIgb2YgR3JhcGhpY3NdKGh0dHA6Ly92aXRhLmhhZC5jby5uei9wYXBlcnMvbGF5ZXJlZC1ncmFtbWFyLnBkZikuIFBhcmEgcmVzb2x2ZXIgc3VzIHByZWd1bnRhcyBzb2JyZSBgZ2dwbG90MmAsIHJlY29taWVuZG8gcXVlIGV4cGxvcmVuIGxhcyBzaWd1aWVudGUgcMOhZ2luYXM6CgotIDxodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tLz4KLSA8aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8+CgrCoUNvbWVuY2Vtb3MhCgojIyBJbXBvcnRhciBEYXRhCgpWb3kgYSBhc3VtaXIgcXVlIHlhIHRpZW5lbiB1bmEgYmFzZSBkZSBkYXRvcy4gTG8gcHJpbWVybyBxdWUgdmFuIGEgdGVuZXIgcXVlIGhhY2VyIGVzIGltcG9ydGFyIGVzYSBiYXNlIGRlIGRhdG9zIGEgYFJgLiBMYXMgYmFzZXMgZGUgZGF0b3MgdmllbmVuIGVuIHRvZG8gdGlwbyBkZSBmb3JtYXMgeSBjb2xvcmVzLCBwZXJvIHNvYnJlIHRvZG8sIGVuIGRpZmVyZW50ZXMgZm9ybWF0b3MuIE5vcyB2YW1vcyBhIGVuZm9jYXIgZW4gbG9zIHNpZ3VpZW50ZXMgZm9ybWF0b3M6CgotICoqLlJkYXRhKio6IGVzdGUgZXMgZWwgZm9ybWF0byBwcm9waW8gZGUgYFJgIHBhcmEgZ3VhcmRhciBpbmZvcm1hY2nDs24uIAogICAgLSBMbyBidWVubzogcHVlZGUgYWxtYW5jZW5lciBgZGF0YWZyYW1lc2AsIGFsIGlndWFsIHF1ZSBvdHJvcyBvYmpldG9zIGNyZWFkb3MgZW4gUi4gRXMgYnVlbm8gcGFyYSBjb21wYWN0YXIgbGEgaW5mb3JtYWNpw7NuLCBwb3IgbG8gdGFudG8gdXRpbGl6YSBtZW5vcyBlc3BhY2lvIGVuIGVsIGRpc2NvLiBFcyBlbCBmb3JtYXRvIHF1ZSBgUmAgbGVlIG3DoXMgcsOhcGlkby4gCiAgICAtIExvIG1hbG86IHVuYSBiYXNlIGRlIGRhdG9zIGd1YXJkYWRhIGVuIGVzdGUgZm9ybWF0byBubyBwdWVkZSBzZXIgYWJpZXJ0YSBwb3Igb3Ryb3MgcHJvZ3JhbWFzIChwb3IgZWouLCBFeGNlbCkuIAotICoqLmNzdioqOiBjb21tYS1zZXBhcmF0ZWQgdmFsdWVzLgogICAgLSBMbyBidWVubzogZXMgYmFzdGFudGUgY29tw7puIHkgcHVlZGUgc2VyIGFiaWVydG8gcG9yIGxhIG1heW9yw61hIGRlIHByb2dyYW1hcyBxdWUgbWFuZWphbiBiYXNlcyBkZSBkYXRvcy4gVGFtYmnDqW4gcHVlZGUgdmVuaXIgY29tbyBleHRlbnNpw7NuICoqLnR4dCoqLgogICAgLSBMbyBtYWxvOiBVdGlsaXphIG3DoXMgZXNwYWNpbyBkZSBkaXNjbyBxdWUgLlJkYXRhIHkgLnhsc3guIE5vIGVzIGJ1ZW5hIHBhcmEgbWFuZWphciBiYXNlcyBkZSBkYXRvcyBxdWUgY29udGllbmVuIHRleHRvLgotICoqLnhsc3gqKjogdW5hIHZlcnNpw7NuIGNvbXByaW1pZGEgZGUgLmNzdi4gCiAgICAtIExvIGJ1ZW5vOiBVdGlsaXphIG1lbm9zIGVzcGFjaW8gZGUgZGlzY28gcXVlIC5jc3YuIEVzIGJhc3RhbnRlIGNvbcO6biB5IHB1ZWRlIHNlciBhYmllcnRvIHBvciBsYSBtYXlvcsOtYSBkZSBwcm9ncmFtYXMgcXVlIG1hbmVqYW4gYmFzZXMgZGUgZGF0b3MuIAogICAgLSBMbyBtYWxvOiBVdGlsaXphIG3DoXMgZXNwYWNpbyBkZSBkaXNjbyBxdWUgLlJkYXRhLgotICoqLnRzdioqOiB0YWItc2VwYXJhdGVkIHZhbHVlcy4gVmVyICoqLmNzdioqLgogICAgCk90cmFzIGV4dGVuc2lvbmVzOgoKLSAqKi5zYXYqKjogZXh0ZW5zacOzbiBkZSBkYXRhIHByb2R1Y2lkYSBwb3IgU1BTUy4gCi0gKiouZHRhKio6IGV4dGVuc2nDs24gZGUgZGF0YSBwcm9kdWNpZGEgcG9yIFN0YXRhLiAKICAgIApFeGlzdGVuIG90cmFzIGV4dGVuc2lvbmVzLiBQYXJhIGNhc2kgdG9kYXMgaGF5IHVuIHBhcXVldGUgZW4gYFJgIHkgdW5hIHLDoXBpZGEgYsO6c3F1ZWRhIGVuIGVsIFtpbnRlcm5ldF0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS8pIGxlcyBkYXLDoSBsYSByZXNwdWVzdGEuCgpJbXBvcnRhciB1biBhcmNoaXZvIGNvbiBleHRlbnNpw7NuICoqLlJkYXRhKiogZXMgbG8gbcOhcyBzaW1wbGU6CgpgYGB7ciBpbXBvcnRfcmRhdGEsIGV2YWw9RkFMU0V9CnNldHdkKCJwYXRoX2RpcmVjdG9yaW8vY2FycGV0YS8iKSAjIE5vIHRlIG9sdmlkZXMgZGUgaW5kaWNhciBlbiBxdcOpIGRpcmVjdG9yaW8gc2UgZW5jdWVudHJhIHR1IGRhdGEuCgpsb2FkKCJ0dV9kYXRhLlJkYXRhIikgCmBgYAoKRWwgcGFxdWV0ZSBgcmVhZHJgIHRpZW5lIGxhcyBmdW5jaW9uZXMgbmVjZXNhcmlhcyBwYXJhIGltcG9ydGFyIGFyY2hpdm9zIGNvbiBleHRlbnNpw7NuICoqLmNzdioqIG8gKioudHh0Kio6CgpgYGB7ciBpbXBvcnRfY3N2LCBldmFsPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpzZXR3ZCgicGF0aF9kaXJlY3RvcmlvL2NhcnBldGEvIikKCiMgSW1wb3J0YXIgZGF0YSBkZSB1bmEgYXJjaGl2byAuY3N2ClNhbGFyaW9zIDwtIHJlYWRfY3N2KCJzYWxhcmlvcy5jc3YiKQoKIyBJbXBvcnRhciBkYXRhIGRlIHVuYSBhcmNoaXZvIC50c3YgbyAudHh0ClNhbGFyaW9zIDwtIHJlYWRfdHN2KCJzYWxhcmlvcy50eHQiKQpTYWxhcmlvcyA8LSByZWFkX3Rzdigic2FsYXJpb3MudHN2IikKYGBgCgpFc3RhIGZ1bmNpw7NuIGFzdW1lIHF1ZSBsYSBwcmltZXJhIGzDrW5lYSBkZSB0dSBiYXNlIGRlIGRhdG9zIGNvbnRpZW5lIGVsIG5vbWJyZSBkZSBsYXMgdmFyaWFibGVzLCBsb3MgdmFsb3JlcyBlc3TDoW4gc2VwYXJhZG9zIHBvciB1bmEgY29tYSBvIHBvciB0YWJzLCB5IGxvcyBOQXMgZXN0w6FuIHJlcHJlc2VudGFkb3MgcG9yIGNlbGRhcyB2YWPDrWFzLiAKCkVsIHBhcXVldGUgYHJlYWR4bGBgIHRpZW5lIGxhcyBmdW5jaW9uZXMgbmVjZXNhcmlhcyBwYXJhIGltcG9ydGFyIGFyY2hpdm9zIGNvbiBleHRlbnNpw7NuICoqLnhscyoqIG8gKioueGxzeCoqOgoKYGBge3IgaW1wb3J0X3hscywgZXZhbD1GQUxTRX0KbGlicmFyeShyZWFkeGwpCnNldHdkKCJwYXRoX2RpcmVjdG9yaW8vY2FycGV0YS8iKQoKIyBJbXBvcnRhciBkYXRhIGRlIHVuYSBhcmNoaXZvIC54bHMgbyAueGxzeApTYWxhcmlvcyA8LSByZWFkX2V4Y2VsKCJzYWxhcmlvcy54bHMiLAogICAgICAgICAgICAgICAgICAgICBzaGVldCA9ICJzaGVldDEiKSAjIE5lY2VzYXJpbyDDum5pY2FtZW50ZSBzaSBsYSBkYXRhIGVzdGEgZW4gdW5hIGhvamEgcXVlIG5vIHNlYSBsYSBwcmltZXJhClNhbGFyaW9zIDwtIHJlYWRfZXhjZWwoInNhbGFyaW9zLnhsc3giLAogICAgICAgICAgICAgICAgICAgICBzaGVldCA9ICJzaGVldDEiKQpgYGAKCkVsIHBhcXVldGUgYHJlYWRzdGF0YTEzYGAgdGllbmUgbGFzIGZ1bmNpb25lcyBuZWNlc2FyaWFzIHBhcmEgaW1wb3J0YXIgYXJjaGl2b3MgY29uIGV4dGVuc2nDs24gKiouZHRhKio6CgpgYGB7ciBpbXBvcnRfZHRhLCBldmFsPUZBTFNFfQpsaWJyYXJ5KHJlYWRzdGF0YTEzKQpzZXR3ZCgicGF0aF9kaXJlY3RvcmlvL2NhcnBldGEvIikKCiMgSW1wb3J0YXIgZGF0YSBkZSB1bmEgYXJjaGl2byAuZHRhClNhbGFyaW9zIDwtIHJlYWQuZHRhMTMoInNhbGFyaW9zLmR0YSIpCmBgYAoKRmluYWxtZW50ZSwgZWwgcGFxdWV0ZSBgZm9yZWlnbmBgIHRpZW5lIGxhcyBmdW5jaW9uZXMgbmVjZXNhcmlhcyBwYXJhIGltcG9ydGFyIGFyY2hpdm9zIGNvbiBleHRlbnNpw7NuICoqLnNhdioqOgoKYGBge3IgaW1wb3J0X3Nwc3MsIGV2YWw9RkFMU0V9CmxpYnJhcnkoZm9yZWlnbikKc2V0d2QoInBhdGhfZGlyZWN0b3Jpby9jYXJwZXRhLyIpCgojIEltcG9ydGFyIGRhdGEgZGUgdW5hIGFyY2hpdm8gLnNhdgpkYXRhc2V0ID0gcmVhZC5zcHNzKCJzYWxhcmlvcy5zYXYiLCAKICAgICAgICAgICAgICAgICAgICB0by5kYXRhLmZyYW1lPVRSVUUpICMgTmVjZXNhcmlvIQpgYGAKCiMjIExpbXBpZXphIGRlIERhdG9zIHkgbGEgVW5pZGFkIGRlIEFuw6FsaXNpcwoKVW5hIHZleiBxdWUgdGllbmVuIGNhcmdhZGEgc3UgYmFzZSBkZSBkYXRvcyBsYSBwb2RlbW9zIGNvbWVuemFyIGEgbWFuaXB1bGFyLiBFbiBlc3RhIHNlY2Npw7NuIHZhbW9zIGEgdmVyIGRvcyBjb25jZXB0b3M6ICp3cmFuZ2xpbmcqIG8gbGEgdHJhbnNmb3JtYWNpw7NuIGRlIHN1IGJhc2UgZGUgZGF0b3MsIHkgZWwgKnNoYXBlKiBkZSBzdSBiYXNlIGRlIGRhdG9zIHkgcXXDqSBmb3JtYSBlcyBsYSBhZGVjdWFkYSBwYXJhIHV0aWxpemFyIGBnZ3Bsb3QyYC4gCgojIyMgKkRhdGEgV3JhbmdsaW5nKgoKTGEgbWFuaXB1bGFjacOzbiB5IHRyYW5zZm9ybWFjacOzbiBkZSBsYSBiYXNlIGRlIGRhdG9zIGVzIGVsIHByaW1lciBwYXNvIGFudGVzIGRlIGluaWNpYXIgc3UgdmlzdWFsaXphY2nDs24uIFJlY3VlcmRlbiwgdG9kYSBudWV2YSB2YXJpYWJsZSBxdWUgdXN0ZWRlcyBlc3TDoW4gY3JlYW5kbyBzZXLDoSB1biBlbGVtZW50byBtw6FzIGRlIGxhIGhpc3RvcmlhIHF1ZSBxdWllcmFuIGNvbnRhci4gRXN0byBubyBzaWduaWZpY2EgcXVlIFRPREFTIGxhcyB2YXJpYWJsZXMgcXVlIGNyZWVuIChlbGltaW5lbikgdmFuIGEgdGVybWluYXIgZGVudHJvIChmdWVyYSkgZGUgdW4gZ3LDoWZpY28uIFNvbG8gc2lnbmlmaWNhIHF1ZSBsYXMgdmFyaWFibGVzIHF1ZSB1c3RlZGVzIGNyZWVuIGV2ZW50dWFsbWVudGUgdGVuZHLDoW4gdW4gcHJvcMOzc2l0bywgZGlyZWN0byBvIGluZGlyZWN0bywgZW4gbGEgaGlzdG9yaWEgcXVlIGN1ZW50ZW4uIAoKU2kgcGVuc2Ftb3MgYSBsYXMgYmFzZXMgZGUgZGF0b3MgY29tbyB1biBtYXRyaXosIGxhIG1hbmlwdWxhY2nDs24gZGUgbGEgZGF0YSBzZSBsYSBwdWVkZSBoYWNlciBlbiBkb3MgZGltZW5zaW9uZXM6IGZpbGFzIChvYnNlcnZhY2lvbmVzL2VqZSB5KSBvIGNvbHVtbmFzICh2YXJpYWJsZXMvZWplIHgpLiBDb21lbmNlbW9zIGNvbiBsYSBtYW5pcHVsYWNpw7NuIGRlIGxhcyBjb2x1bW5hcy92YXJpYWJsZXMuIAoKIyMjIyBTZWxlY2Npb25hciwgUmVub21icmFyLCBDcmVhciBWYXJpYWJsZXMKCkxhIGZ1bmNpw7NuIGBzZWxlY3RgIGRlbCBwYXF1ZXRlIGB0aWR5dmVyc2VgIG5vcyBwZXJtaXRlIGxpbWl0YXIgbyBzZWxlY2Npb25hciBsYXMgdmFyaWFibGVzIGRlIG51ZXN0cmEgYmFzZSBkZSBkYXRvcy4gCgpgYGB7ciBzZWxlY3QsIGV2YWw9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQoKIyBNYW50ZW5lciBsYXMgdmFyaWFibGVzIG5vbWJyZSwgZXN0YXR1cmEgeSBnw6luZXJvCm51ZXZhX2RhdGEgPC0gdmllamFfZGF0YSAlPiUKICBzZWxlY3Qobm9tYnJlLCBlc3RhdHVyYSwgZ2VuZXJvKQoKIyBNYW50ZW5lciBsYXMgdmFyaWFibGVzIG5vbWJyZSwgeSB0b2RhcyBsYXMgcXVlIHNlIGVuY3VlbnRyZW4gZW50cmUgbWFzYSB5IGVzcGVjaWUKbnVldmFfZGF0YSA8LSB2aWVqYV9kYXRhICU+JQogIHNlbGVjdChub21icmUsIG1hc2E6ZXNwZWNpZSkKCiMgTWFudGVuZXIgdG9kYXMgbGFzIHZhcmlhYmxlcyBleGNlcHRvIGVkYWQgeSBnZW5lcm8KbnVldmFfZGF0YSA8LSB2aWVqYV9kYXRhICU+JQogIHNlbGVjdCgtZWRhZCwgLWdlbmVybykKCmBgYAoKUG9kZW1vcyB1dGlsaXphciBgY29sbmFtZXNgIHBhcmEgdmVyIGVsIG5vbWJyZSBkZSBsYSB2YXJpYWJsZXMgeSB0YW1iacOpbiBwYXJhIHJlbm9tYnJhciBsYXMgdmFyaWFibGVzLiAKCmBgYHtyIGNvbG5hbWVzMX0KIyBWZXIgZWwgbm9tYnJlIGRlIGxhcyB2YXJpYWJsZXMgZGUgbGEgYmFzZSBkZSBkYXRvcyBzdGFyd2FycyoKY29sbmFtZXMoc3RhcndhcnMpCgojICpzdGFyd2FycyBlcyB1bmEgYmFzZSBkZSBkYXRvcyBkZSBtdWVzdHJhIHF1ZSBlc3TDoSBpbmNsdWlkYSBlbiBlbCBsaWJyZXLDrWEgdGlkeXZlcnNlCmBgYAoKYGBge3IgY29sbmFtZXMyfQojIFJlbm9tYnJhciBsYSBwcmltZXIgdmFyaWFibGUKY29sbmFtZXMoc3RhcndhcnMpWzFdIDwtICJub21icmUiCmNvbG5hbWVzKHN0YXJ3YXJzKQpgYGAKCmBgYHtyIGNvbG5hbWVzM30KIyBSZW5vbWJyYXIgZGUgbGEgcHJpbWVyIGEgbGEgY3VhcnRhIHZhcmlhYmxlCmNvbG5hbWVzKHN0YXJ3YXJzKVtjKDE6NCldIDwtIGMoIm5vbWJyZSIsImVzdGF0dXJhIiwibWFzYSIsImNvbG9yX3BlbG8iKQpjb2xuYW1lcyhzdGFyd2FycykKYGBgCgojIyMjIEZpbHRyYXIgT2JzZXJ2YWNpb25lcwoKVGFtYmnDqW4gaGFicsOhbiBvY2FzaW9uZXMgZW4gbGFzIHF1ZSBxdWllcmFuIGVsaW1pbmFyIGNpZXJ0YXMgZmlsYXMvb2JzZXJ2YWNpb25lcyBvIGZpbGFzIHF1ZSB0ZW5nYW4gY2llcnRhcyBjYXJhY3RlcsOtc3RpY2FzLiBQYXJhIGVsaW1pbmFyIG9ic2VydmFjaW9uZXMgbWFudWFsbWVudGUgcG9kZW1vcyB1c2FyIGxhcyBmdW5jaW9uZXMgYmFzZSBkZSBgUmAuIFZlYW1vcyBjw7NtbyBzZSB2ZSBudWVzdHJhIGJhc2UgZGUgZGF0b3MuIAoKYGBge3IgZGZ9CiMgVmVyIGRhdGEKaGVhZChzdGFyd2FycykgIyBOb3RhOiBzb2xvIHNlIG11ZXN0cmEgdW5hIHBhcnRlIGRlIGxhIGJhc2UgZGUgZGF0b3MKYGBgCgpMaXN0by4gU2kgcXVpc2llcmFuIGVsaW1pbmFyIHNvbG8gbGEgc2VndW5kYSBvYnNlcnZhY2nDs24sIHBvZHLDrWFuIGhhY2VyIGxvIHNpZ3VpZW50ZToKCmBgYHtyIGVsaW1yb3d9CiMgRWxpbWluYXIgbWFudWFsbWVudGUgbGEgc2VndW5kYSBvYnNlcnZhY2nDs24Kc3RhcndhcnNfbm8yIDwtIHN0YXJ3YXJzWy0yLF0KaGVhZChzdGFyd2Fyc19ubzIpCmBgYAoKVGFtYmnDqW4gcG9kZW1vcyBlbGltaW5hciBkZSBsYXMgc2VndW5kYSBhIGxhIGN1YXJ0YSBmaWxhOgoKYGBge3IgZWxpbXJvdzJ9CiMgRWxpbWluYXIgbWFudWFsbWVudGUgZGUgbGEgc2VndW5kYSBhIGxhIGN1YXJ0YSBvYnNlcnZhY2nDs24Kc3RhcndhcnNfbm8yNCA8LSBzdGFyd2Fyc1stYygyOjQpLF0KaGVhZChzdGFyd2Fyc19ubzI0KQpgYGAKClBvciBsbyBnZW5lcmFsLCBubyBlcyB1bmEgbWFuZXJhIG11eSBwcsOhY3RpY2EgZGUgZmlsdHJhciBpbmZvcm1hY2nDs24uIFVuYSBtZWpvciBtYW5lcmEgZXMgZmlsdHJhciBsYSBpbmZvcm1hY2nDs24gZGUgYWN1ZXJkbyBhIGNpZXJ0YXMgY2FyYWN0ZXLDrXN0aWNhcyBxdWUgZW5jb250cmFtb3MgZW4gbGEgcHJvcGlhIGRhdGEuIFBhcmEgZXNvIHV0aWxpemFtb3MgbGEgZnVuY2nDs24gYGZpbHRlcmAuIAoKYGBge3IgZmlsdGVyMX0KIyBRdWVkYXJzZSDDum5pY2FtZW50ZSBjb24gb2JzZXJ2YWNpb25lcyBjdXlhIGhvbWV3b3JsZCBzZWEgIlRhdG9vaW5lIgp0YXRvb2luZSA8LSBzdGFyd2FycyAlPiUKICBmaWx0ZXIoaG9tZXdvcmxkPT0iVGF0b29pbmUiKQpoZWFkKHRhdG9vaW5lKQpgYGAKCmBgYHtyIGZpbHRlcjJ9CiMgRWxpbWluYXIgw7puaWNhbWVudGUgb2JzZXJ2YWNpb25lcyBjdXlhIGhvbWV3b3JsZCBzZWEgIlRhdG9vaW5lIgpub190YXRvb2luZSA8LSBzdGFyd2FycyAlPiUKICBmaWx0ZXIoaG9tZXdvcmxkIT0iVGF0b29pbmUiKQpoZWFkKG5vX3RhdG9vaW5lKQpgYGAKCiMjIyMgQ3JlYXIvTW9kaWZpY2FyIFZhcmlhYmxlcywgQWdydXBhciwgeSBBZ3JlZ2FyL1JlY29ydGFyIERhdG9zCgpMYSBmdW5jacOzbiBwYXJhIGNyZWFyL21vZGlmaWNhciB2YXJpYWJsZXMgZXMgYG11dGF0ZWAuIAoKYGBge3IgbW9kaWZpY2FyLCBldmFsPUZBTFNFfQojIFBhc2FyIGxhIGFsdHVyYSBxdWUgZXN0w6EgZW4gY2VudMOtbWV0cm9zIGEgcHVsZ2FkYXMgeSBsYSBtYXNhIHF1ZSBlc3TDoSBlbiBraWxvZ3JhbW9zIGEgbGlicmFzCm5ld2RhdGEgPC0gbXV0YXRlKHN0YXJ3YXJzLCAKICAgICAgICAgICAgICAgICAgZXN0YXR1cmEgPSBlc3RhdHVyYSAqIDAuMzk0LAogICAgICAgICAgICAgICAgICBtYXNhICAgPSBtYXNhICAgKiAyLjIwNSkKCiMgVGFtYmnDqW4gcG9kZW1vcyBjcmVhciBudWV2YXMgdmFyaWFibGVzCm5ld2RhdGEgPC0gc3RhcndhcnMgJT4lCiAgbXV0YXRlKGVzdGF0dXJhX2luID0gZXN0YXR1cmEgKiAwLjM5NCwKICAgICAgICAgbWFzYV9sYiAgID0gbWFzYSAgICogMi4yMDUpCmBgYAoKUG9yIGxvIGdlbmVyYWwsIHZhbW9zIGEgY3JlYXIgdmFyaWFibGVzIGJhc2FkYXMgZW4gY29uZGljaW9uZXMgZGUgb3RyYXMgdmFyaWFibGVzIGVuIG51ZXN0cmEgZGF0YS4gCgpgYGB7ciBjcmVhciwgZXZhbD1GQUxTRX0KIyBTaSBsYSBhbHR1cmEgZXMgbWF5b3IgYSAxODAgY20gaGVpZ2h0Y2F0ID0gImFsdG8iLCBjYXNvIGNvbnRyYXRpbyBoZWlnaHRjYXQgPSAiY2hhcGFycm8iLCAgIApuZXdkYXRhIDwtIG11dGF0ZShzdGFyd2FycywgCiAgICAgICAgICAgICAgICAgIGVzdGF0dXJhY2F0ID0gaWZlbHNlKGVzdGF0dXJhID4gMTgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhbHRvIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY2hhcGFycm8iKQogICAgICAgICAgICAgICAgICAKIyBDb252ZXJ0aXIgY3VhbHF1aWVyIGNvbG9yIGRlIG9qb3MgcXVlIG5vcyBzZWEgYmxhY2ssIGJsdWUgbyBicm93biwgZW4gIm90cm8iCm5ld2RhdGEgPC0gbXV0YXRlKHN0YXJ3YXJzLCAKICAgICAgICAgICAgICAgICAgZXllX2NvbG9yID0gaWZlbHNlKGV5ZV9jb2xvciAlaW4lIGMoImJsYWNrIiwgImJsdWUiLCAiYnJvd24iKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV5ZV9jb2xvciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJvdHJvIikKICAgICAgICAgICAgICAgICAgCiMgQ3VhbHF1aWVyIGVzdGF0dXJhIG3DoXMgcXVlIDIwMCB5IG1lbm9zIHF1ZSA3NSBsYSBwYXNhbW9zIGEgTkEKbmV3ZGF0YSA8LSBtdXRhdGUoc3RhcndhcnMsIAogICAgICAgICAgICAgICAgICBlc3RhdHVyYSA9IGlmZWxzZShlc3RhdHVyYSA8IDc1IHwgZXN0YXR1cmEgPiAyMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVzdGF0dXJhKQoKYGBgCgpUYW1iacOpbiBwb2RlbW9zIGNyZWFyIG51ZXZhcyB2YXJpYWJsZXMgcXVlIHNlYW4gcHJvZHVjdG8gZGUgb3BlcmFjaW9uZXMuIEluY2x1c28gbnVldmFzIHZhcmlhYmxlcyBxdWUgc2VhbiBwcm9kdWN0b3MgZGUgb3BlcmFjaW9uZXMgZGUgZGF0b3MgcHJldmlhbWVudGUgYWdydXBhZG9zLgoKYGBge3IgZ3JvdXBieX0KIyBDYWxjdWxhciBlbCBwcm9tZWRpbyBkZSBlc3RhdHVyYSBkZSBsb3MgcGVyc29uYWplcyBlbiBsYSBiYXNlIGRlIGRhdG9zCnN0YXJ3YXJzICU+JQogIG11dGF0ZShtZWFuX2VzdCA9IG1lYW4oZXN0YXR1cmEsIG5hLnJtID0gVFJVRSkpICU+JQogIHNlbGVjdChtZWFuX2VzdCkgCgojIEVzIGxvcyBtaXNtbyBxdWUgaGFjZXIgZXN0bzoKbWVhbihzdGFyd2FycyRlc3RhdHVyYSwgbmEucm0gPSBUUlVFKQoKIyBDYWxjdWxhciBlbCBwcm9tZWRpbyBkZSBlc3RhdHVyYSBwb3IgZXNwZWNpZToKc3RhcndhcnMgJT4lCiAgZ3JvdXBfYnkoc3BlY2llcykgJT4lCiAgbXV0YXRlKG1lYW5fZXN0ID0gbWVhbihlc3RhdHVyYSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgc2VsZWN0KHNwZWNpZXMsbWVhbl9lc3QsZXN0YXR1cmEpIApgYGAKCkhheSBvY2FzaW9uZXMgZW4gbGFzIHF1ZSBubyBxdWVyZW1vcyBtYW50ZW5lciB0b2RhcyBlc2FzIG9ic2VydmFjaW9uZXMgcmVwZXRpZGFzIHBhcmEgZXNhIHZhcmlhYmxlIGVuIHBhcnRpY3VsYXIuIFBhcmEgZXN0bywgdXRpbGl6YW1vcyBsYSBmdW5jacOzbiBgZGlzdGluY3RgLgoKYGBge3IgZGlzdGluY3R9CiMgRWxpbWluYXIgb2JzZXJ2YWNpb25lcyByZXBldGlkYXMgZGUgZXNwZWNpZQpzdGFyd2FycyAlPiUKICBncm91cF9ieShzcGVjaWVzKSAlPiUKICBtdXRhdGUoZXN0YXR1cmFfaHQgPSBtZWFuKGVzdGF0dXJhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBkaXN0aW5jdChzcGVjaWVzLCAua2VlcF9hbGw9VFJVRSkgJT4lCiAgc2VsZWN0KHNwZWNpZXMsZXN0YXR1cmFfaHQpIApgYGAKCiMjIyBMYSBVbmlkYWQgZGUgQW7DoWxpc2lzCgpMbyBwcmltZXJvIHF1ZSBkZWJlbiBzYWJlciBkZSBzdSBkYXRhIGVzIGxhICp1bmlkYWQgZGUgYW5hbMOhc2lzKjogbGEgdW5pZGFkIHByaW5jaXBhbCBhIGxhIHF1ZSBzZSBlc3TDoSBvYnNlcnZhbmRvIGVuIHVuYSBiYXNlIGRlIGRhdG9zLiBQb3IgZWplbXBsbywgZW4gbGEgYmFzZSBkZSBkYXRvcyBkZSBgc3RhcndhcnNgLCBsYSB1bmlkYWQgZGUgYW7DoWxpc2lzIGVyYSBlbCBwZXJzb25hamUuIFRvZGEgbGEgaW5mb3JtYWNpw7NuIGRlIGxhcyB2YXJpYWJsZXMgZXJhIHNvYnJlIGVsIHBlcnNvbmFqZS4gCgpgYGB7ciBkZjJ9CiMgVmVyIGRhdGEKaGVhZChzdGFyd2FycykgIyBOb3RhOiBzb2xvIHNlIG11ZXN0cmEgdW5hIHBhcnRlIGRlIGxhIGJhc2UgZGUgZGF0b3MKYGBgCgpFbiBsYSBzaWd1aWVudGUgYmFzZSBkZSBkYXRvcywgbGEgdW5pZGFkIGRlIGFuw6FsaXNpcyBlcyBlbCBwYcOtcy1hw7FvLiBFcyBkZWNpciwgbGEgaW5mb3JtYWNpw7NuIGRlIGNhZGEgdmFyaWFibGUgZXMgc29icmUgdW4gcGHDrXMgZW4gdW4gYcOxbyBlc3BlY8OtZmljby4gCgpgYGB7ciBkZnBhbmVsfQpsaWJyYXJ5KHJlYWRzdGF0YTEzKQoKcGFuZWwgPC0gcmVhZC5kdGExMygiaHR0cDovL2Rzcy5wcmluY2V0b24uZWR1L3RyYWluaW5nL1BhbmVsMTAxLmR0YSIpCmhlYWQocGFuZWwsMjApCmBgYAoKUG9kZW1vcyBtb2RpZmljYXIgbGEgdW5pZGFkIGRlIGFuw6FsaXNpcyB1dGlsaXphbmRvIHVuYSBkZSBsYXMgdMOpY25pY2FzIHByZXNlbnRhZGFzIGFudGVzLiBQb3IgZWplbXBsbywgcG9kZW1vcyBjYW1iaWFyIGxhIHVuaWRhZCBkZSBhbsOhbGlzaXMgZGUgbnVlc3RyYSBiYXNlIGRlIGRhdG9zIGBwYW5lbGAgYWwgYcOxbzoKCmBgYHtyIGRmcGFuZWx5ZWFyfQpwYW5lbCAlPiUKICBncm91cF9ieSh5ZWFyKSAlPiUKICBtdXRhdGUoeV9tZWFuX3llYXIgPSBtZWFuKHksIG5hLnJtPVRSVUUpKSAlPiUKICBkaXN0aW5jdCh5ZWFyLC5rZWVwX2FsbD1UKSAlPiUKICBzZWxlY3QoeWVhcix5X21lYW5feWVhcikKYGBgCgpMbyBwcmltZXJvIHF1ZSBkZWJlbiBoYWNlciBhbnRlcyBkZSBjb21lbnphciBhIGV2YWx1YXIgbGEgZGF0YSwgZXMgc2FiZXIgbGEgdW5pZGFkIGRlIGFuw6FsaXNpcy4gRXMgZGVjaXIsIGRlIHF1acOpbiBlc3RhbW9zIGhhYmxhbmRvIGN1YW5kbyB2YW1vcyBhIGNvbnRhciBudWVzdHJhIGhpc3RvcmlhLiAKCiMjIEludHJvZHVjY2nDs24gYSBgZ2dwbG90MmAKCmBnZ3Bsb3QyYCBlcyB1biBwYXF1ZXRlIHF1ZSBlc3TDoSBpbmNsdcOtZG8gZW4gYHRpZHl2ZXJzZWAgeSBub3MgcGVybWl0ZSBjcmVhciBncsOhZmljb3MgZW4gY2FwYXMuIFBvZGVtb3MgY29uc3RydWlyIHVuIGdyw6FmaWNvIGNvbXBsZWpvIGNvbWVuemFuZG8gcG9yIGFsZ28gc2ltcGxlIGUgaXIgYWdyZWdhbmRvIGVsZW1lbnRvcywgdW5vIGEgbGEgdmV6LiAKClBhcmEgY29uc3RydWlyIHVuIGdyw6FmaWNvIGxhIHByaW1lcmEgZnVuY2nDs24gcXVlIG5lY2VzaXRhcyBlcyBgZ2dwbG90YCBxdWUgZXNwZWNpZmljYSAKCjEuIExhIGRhdGEgcXVlIHZhcyBhIHV0aWxpemFyIAoyLiBFbCBtYXBlbyAoYG1hcHBpbmdgKSBkZSBsYXMgdmFyaWFibGVzIGEgcHJvcGllZGFkZXMgdmlzdWFsZXMuIEVsIGBtYXBwaW5nYCBzZSBsbyBwb25lIGRlbnRybyBkZSBsYSBmdW5jacOzbiBgYWVzYCAocXVlIHNpZ25pZmljYSAqYWVzdGhldGljcyopLiAKCmBgYHtyIGdncGxvdH0KIyBDYXJnYSBiYXNlIGRlIGRhdG9zCmRhdGEoQ1BTODUgLCBwYWNrYWdlID0gIm1vc2FpY0RhdGEiKQoKIyBFc3BlY2lmaWNhciBsYSBkYXRhIHkgZWwgbWFwZW8KQ1BTODUgJT4lICMgRXN0byBlcyBzdWZpY2llbnRlIHBhcmEgZXNwZWNpZmljYXIgbGEgZGF0YQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBleHBlciwgeSA9IHdhZ2UpKQoKIyBMbyBtaXNtbyBxdWUgYW50ZXMgcGVybyBlc2NyaXRvIGRpZmVyZW50ZQojIGdncGxvdChkYXRhID0gQ1BTODUsIG1hcHBpbmcgPSBhZXMoeCA9IGV4cGVyLCB5ID0gd2FnZSkpCmBgYAoKTm8gaGF5IG5hZGEuIMK/UG9yIHF1w6k/IEVzcGVjaWZpY2Ftb3MgcXVlIGxhIHZhcmlhYmxlICpleHBlciogZGViZSBlc3RhciBtYXBlYWRhIGVuIGVsIGVqZSB4IHkgcXVlIGxhIHZhcmlhYmxlICp3YWdlKiBkZWJlIGVzdGFyIGVuIGVsIGVqZSB5LCBwZXJvIG5vIGhlbW9zIGVzcGVjaWZpY2FkbyBxdcOpIHF1ZXJlbW9zIHBvbmVyIGVuIGVzZSBncsOhZmljby4gCgojIyMgKmdlb21zKgoKTG9zICpnZW9tcyogc29uIG9iamV0b3MgZ2VvbcOpdHJpY29zIChwdW50b3MsIGzDrW5lYXMsIGJhcnJhcywgZXRjLikgcXVlIHB1ZWRlbiBzZXIgY29sb2NhZG9zIGVuIHVuIGdyw6FmaWNvLiBTZSBsYXMgYWdyZWdhIHV0aWxpemFuc28gZnVuY2lvbmVzIHF1ZSBjb21pZW56YW4gY29uIGBnZW9tX2AuIEVuIGVzdGUgZWplbXBsbywgdmFtb3MgYSBhZ3JlZ2FyIHB1bnRvcyB1c2FuZG8gbGEgZnVuY2nDs24gYGdlb21fcG9pbnRgIHkgYXPDrSBjcmVhcmVtb3MgdW4gKnNjYXR0ZXJwbG90Ki4KCkVuIGBnZ3Bsb3QyYCwgbGFzIGZ1bmNpb25lcyBlc3TDoW4gZW5sYXphZGFzIHV0aWxpemFuZG8gdW4gKiorKiogcGFyYSBjb25zdHJ1aXIgZWwgZ3LDoWZpY28gZmluYWwuIAoKYGBge3IgcGxvdDF9CkNQUzg1ICU+JSAKICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gZXhwZXIsIHkgPSB3YWdlKSkgKwogIGdlb21fcG9pbnQoKQpgYGAKCsK/UXVlamVzbz8gwr9VbiAqb3V0bGllcio/IFB1ZXMgbG8gZWxpbWluYW1vczoKCmBgYHtyIHBsb3QyfQpDUFM4NSAlPiUgCiAgZmlsdGVyKHdhZ2U8MzApICU+JQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBleHBlciwgeSA9IHdhZ2UpKSsKICBnZW9tX3BvaW50KCkKYGBgCgpCZWxsZXphLiBVbmEgc2VyaWUgZGUgb3BjaW9uZXMgcHVlZGVuIHNlciBtb2RpZmljYWRhcyBkZW50cm8gZGUgdW4gYGdlb21fYC4gRXN0YXMgaW5jbHV5ZW4gYGNvbG9yYCwgYHNpemVgLCB5IGBhbHBoYWAuIEVzdG9zIGNvbnRyb2xhbiBlbCBjb2xvciwgZWwgdGFtYcOxbyB5IGxhIHRyYW5zcGFyZW5jaWEuIExhIHRyYW5zcGFyZW5jaWEgdmEgZGUgMCAoY29tcGxldGFtZW50ZSB0cmFuc3BhcmVudGUpIGEgMSAoY29tcGxldGFtZW50ZSBvcGFjbykuIExhIHRyYW5zcGFyZW5jaWEgc2lydmUgcGFyYSB2aXN1YWxpemFyIG1lam9yIGxvcyBlbGVtZW50b3MgcXVlIHNlIHN1cGVycG9uZW4gKHkgYWRlbcOhcyBoYWNlIHF1ZSBzdXMgZ3LDoWZpY29zIHNlIHZlYW4gYm9uaXRvcykuIAoKYGBge3IgcGxvdDN9CkNQUzg1ICU+JSAKICBmaWx0ZXIod2FnZTwzMCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZXhwZXIsIHkgPSB3YWdlKSkgKwogIGdlb21fcG9pbnQoY29sb3IgPSAiY29ybmZsb3dlcmJsdWUiLAogICAgICAgICAgICAgYWxwaGEgPSAuNywKICAgICAgICAgICAgIHNpemUgPSAzKQpgYGAKCkFob3JhLCBwb2RlbW9zIGFncmVnYXIgbGEgcmVjdGEgZGUgbWVqb3IgYWp1c3RlICgqYmVzdCBmaXQgbGluZSopIHV0aWxpemFuZG8gbGEgZnVuY2nDs24gYGdlb21fc21vb3RoYC4gTGFzIG9wY2lvbmVzIHBhcmEgY2FkYSBgZ2VvbV9gIGxhcyBwdWVkZW4gY29uc3VsdGFyIHV0aWxpemFuZG8gZWwgY29tYW5kbyBgaGVscCgpYC4gRW4gZWwgY2FzbyBkZSBgZ2VvbV9zbW9vdGhgIHBvZGVtb3MgZ3JhZmljYXIgdW5hIGzDrW5lYSBiYXNhZGEgZW4gdW4gbW9kZWxvIGxpbmVhbCAobG0pLiAKCmBgYHtyIHBsb3RzbW9vdGh9CkNQUzg1ICU+JSAKICBmaWx0ZXIod2FnZTwzMCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZXhwZXIsIHkgPSB3YWdlKSkgKwogIGdlb21fcG9pbnQoY29sb3IgPSAiY29ybmZsb3dlcmJsdWUiLAogICAgICAgICAgICAgYWxwaGEgPSAuNywKICAgICAgICAgICAgIHNpemUgPSAzKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwKICAgICAgICAgICAgICBjb2xvciA9ICJncmV5IikKYGBgCgpJbmNyZW1lbnRhciBsYSBleHBlcmllbmNpYSBlc3TDoSBjb3JyZWxhY2lvbmFkbyBjb24gaW5jcmVtZW50b3MgZW4gZWwgc2FsYXJpby4gCgojIyMgQWdydXBhciBwb3IgQ2FyYWN0ZXLDrXN0aWNhcyBkZSBsYSBEYXRhCgpBZGVtw6FzIGRlIG1hcGVhciBsYXMgdmFyaWFibGUgYWwgZWplIHggeSBhbCBlamUgeSwgbGFzIHZhcmlhYmxlcyBwdWVkZW4gc2VyIG1hcGVhZGFzIGEgdW4gYGNvbG9yYCwgYHNoYXBlYCwgYHNpemVgLCBgYWxwaGFgLCB5IG90cmFzIGNhcmFjdGVyw61zdGljYXMgdmlzdWFsZXMgZGUgb2JqZXRvcyBnZW9tw6l0cmljb3MuIEVzdG9zIG5vcyBwZXJtaXRlIGRpZmVyZW5jaWFyIGdydXBvcyBkZSBvYnNlcnZhY2lvbmVzIGVuIGVsIG1pc21vIGdyw6FmaWNvLiBBZ3JlZ3VlbW9zIGVsIGfDqW5lcm8gZW4gZWwgZ3LDoWZpY28geSByZXByZXNlbnTDqW1vc2xvIHBvciBjb2xvci4gCgpgYGB7ciBnZ3Bsb3Rjb2xvcn0KQ1BTODUgJT4lIAogIGZpbHRlcih3YWdlPDMwKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBleHBlciwKICAgICAgICAgICAgIHkgPSB3YWdlLAogICAgICAgICAgICAgY29sb3IgPSBzZXgpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IC43LAogICAgICAgICAgICAgc2l6ZSA9IDMpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCAKICAgICAgICAgICAgICBzaXplID0gMS41KQpgYGAKCkxhIG9wY2nDs24gZGUgYGNvbG9yYCBsYSBwb25lbW9zIGRlbnRybyBkZSBsYSBmdW5jaW9uIGBhZXNgIHBvcnF1ZSBlc3RhbW9zIG1hcGVhbmRvIGxhIHZhcmlhYmxlIGEgdW4gYWVzdGhldGljLiAKClBhcmVjZXLDrWEgcXVlIGxvcyBob21icmVzIHRpZW5kZW4gYSBnYW5hciBtw6FzIGRpbmVybyBxdWUgbGFzIG11amVyZXMgeSBxdWUgbGEgcmVsYWNpw7NuIGVudHJlIGV4cGVyaWVuY2lhIHkgc2FsYXJpbyBlcyBtw6FzIGZ1ZXJ0ZSBwYXJhIGhvbWJyZXMgcXVlIHBhcmEgbXVqZXJlcy4gUGF0cmlhcmNhZG8gcXVlIGxlIGxsYW1hbi4gCgojIyMgU2NhbGVzCgpMYXMgYHNjYWxlc2AgY29udHJvbGFuIGxhcyBjYXJhY3RlcsOtc3RpY2FzIHZpc3VhbGVzIGRlbCBncsOhZmljby4gTGFzIGZ1bmNpb25lcyBgc2NhbGVzX2AgdGUgcGVybWl0ZW4gbW9kaWZpY2FyIGVsIG1hcGVvLiAKCmBgYHtyIGdncGxvdHNjYWxlc30KQ1BTODUgJT4lIAogIGZpbHRlcih3YWdlPDMwKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBleHBlciwKICAgICAgICAgICAgIHkgPSB3YWdlLAogICAgICAgICAgICAgY29sb3IgPSBzZXgpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIAogICAgICAgICAgICAgIHNpemUgPSAxLjUpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDYwLCAxMCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDMwLCA1KSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBzY2FsZXM6OmRvbGxhcikgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249IkUiLGFscGhhID0gMC41KQpgYGAKCllhIHNlIHZlIG1lam9yLiBIYXkgdW5vcyBzaWdub3MgZGUgZMOzbGFyZXMgeSBsb3MgY29sb3JlcyBlc3TDoW4gbcOhcyBib25pdG9zLiBEaWdhbW9zIHF1ZSBxdWVyZW1vcyB2ZXIgc2kgbGEgcmVsYWNpw7NuIGRlIGfDqW5lcm8gZW50cmUgZXhwZXJpZW5jaWEgeSBzYWxhcmlvIHNlIG1hbnRpZW5lIHBhcmEgdG9kb3MgbG9zIHNlY3RvcmVzLiBQYXJhIGVzbyB1dGlsaXphbW9zLi4uCgojIyMgRmFjZXRzCgpgRmFjZXRzYCByZXByb2R1Y2VuIGVsIGdyw6FmaWNvIHBhcmEgY2FkYSBuaXZlbCBkZSB1bmEgdmFyaWFibGUgKG8gYWxndW5hcyB2YXJpYWJsZXMpLiBFbiBlc3RlIGNhc28sIGxvcyBncsOhZmljb3MgZXN0YXLDoW4gZGl2aWRpZG9zIHBhcmEgY2FkYSBuaXZlbCBkZSBsYSB2YXJpYWJsZSBgc2VjdG9yYC4gCgpgYGB7ciBnZ3Bsb3RmYWNldH0KQ1BTODUgJT4lIAogIGZpbHRlcih3YWdlPDMwKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBleHBlciwKICAgICAgICAgICAgIHkgPSB3YWdlLAogICAgICAgICAgICAgY29sb3IgPSBzZXgpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIAogICAgICAgICAgICAgIHNpemUgPSAxLjUpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDYwLCAxMCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDMwLCA1KSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBzY2FsZXM6OmRvbGxhcikgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249IkUiLGFscGhhID0gMC41KSArCiAgZmFjZXRfd3JhcCh+c2VjdG9yKQpgYGAKCiMjIyBMYWJlbHMKClVuIGdyw6FmaWNvIGRlYmUgc2VyIGbDoWNpbCBkZSBpbnRlcnByZXRhci4gRWwgbGVjdG9yIG5vIHRpZW5lIHF1ZSBlc3RhciBhZGl2aW5hbmRvIGNhZGEgbm9tYnJlIHF1ZSBzZSBsZXMgb2N1cnJpw7MgcG9uZXIgYSBsYXMgdmFyaWFibGVzIG5pIHF1w6kgc2lnbmlmaWNhIGNhZGEgZWplIG5pIGNhZGEgY29sb3IgbmkgY2FkYSBtb3ZpZGEuIExhIGZ1bmNpw7NuIGBsYWJzYCBub3MgcGVybWl0ZSBjYW1iaWFyIGVzby4gCgpgYGB7ciBnZ3Bsb3RzbGFiZWxzfQpDUFM4NSAlPiUgCiAgZmlsdGVyKHdhZ2U8MzApICU+JQogIGdncGxvdChhZXMoeCA9IGV4cGVyLAogICAgICAgICAgICAgeSA9IHdhZ2UsCiAgICAgICAgICAgICBjb2xvciA9IHNleCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAzKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgCiAgICAgICAgICAgICAgc2l6ZSA9IDEuNSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgNjAsIDEwKSkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMzAsIDUpLAogICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHNjYWxlczo6ZG9sbGFyKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbj0iRSIsYWxwaGEgPSAwLjUpICsKICBmYWNldF93cmFwKH5zZWN0b3IpICsKICBsYWJzKHRpdGxlID0gIlJlbGFjacOzbiBlbnRyZSBzYWxhcmlvIHkgZXhwZXJpZW5jaWEiLAogICAgICAgc3VidGl0bGUgPSAiRW5jdWVzdGEgZGUgUG9ibGFjacOzbiIsCiAgICAgICBjYXB0aW9uID0gImZ1ZW50ZTogaHR0cDovL21vc2FpYy13ZWIub3JnLyIsCiAgICAgICB4ID0gIkHDsW9zIGRlIEV4cGVyaWVuY2lhIiwKICAgICAgIHkgPSAiU2FsYXJpbyBwb3IgSG9yYSIsCiAgICAgICBjb2xvciA9ICJHw6luZXJvIikKYGBgCgojIyMgVGVtYXMKCkZpbmFsbWVudGUsIGxlIHBvZGVtb3MgZGFyIGxvcyDDumx0aW1vcyB0b3F1ZXMgcGFyYSBxdWUgbG9zIGdyw6FmaWNvcyBxdWVkZW4gcGVwYSwgcGFyYSBxdWUgdGVuZ2EgZXNlIGplIG5lIHNhaXMgcXVvaSwgZXMgc29tZXRoaW5nIHNvbWV0aGluZy4gTGEgZnVuY2lvbiBgdGhlbWVfYCBub3MgcGVybWl0ZSBjb250cm9sYXIgY29sb3JlcyBkZWwgYmFja2dyb3VuZCwgZnVlbnRlcywgbMOtbmVhcywgbGV5ZW5kYXMsIHkgb3Ryb3MgZWxlbWVudG9zIHF1ZSBubyBlc3TDoW4gcmVsYWNpb25hZG9zIGEgbnVlc3RyYSBkYXRhLiBFbCBncsOhZmljbyBmaW5hbCBzZSBwdWVkZSB2ZXIgYWxnbyBhc8OtLiAKCmBgYHtyIGdncGxvdHRoZW1lfQpDUFM4NSAlPiUgCiAgZmlsdGVyKHdhZ2U8MzApICU+JQogIGdncGxvdChhZXMoeCA9IGV4cGVyLAogICAgICAgICAgICAgeSA9IHdhZ2UsCiAgICAgICAgICAgICBjb2xvciA9IHNleCwKICAgICAgICAgICAgIGZpbGwgPSBzZXgpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIAogICAgICAgICAgICAgIHNpemUgPSAxLjUpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDYwLCAxMCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDMwLCA1KSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBzY2FsZXM6OmRvbGxhcikgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249IkUiLGFscGhhID0gMC41KSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uPSJFIixhbHBoYSA9IDAuNSkgKwogIGZhY2V0X3dyYXAofnNlY3RvcikgKwogIGxhYnModGl0bGUgPSAiUmVsYWNpw7NuIGVudHJlIHNhbGFyaW8geSBleHBlcmllbmNpYSIsCiAgICAgICBzdWJ0aXRsZSA9ICJFbmN1ZXN0YSBkZSBQb2JsYWNpw7NuIiwKICAgICAgIGNhcHRpb24gPSAiZnVlbnRlOiBodHRwOi8vbW9zYWljLXdlYi5vcmcvIiwKICAgICAgIHggPSAiQcOxb3MgZGUgRXhwZXJpZW5jaWEiLAogICAgICAgeSA9ICJTYWxhcmlvIHBvciBIb3JhIiwKICAgICAgIGNvbG9yID0gIkfDqW5lcm8iLAogICAgICAgZmlsbCA9ICdHw6luZXJvJykgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpIApgYGAKCioqwqFCRS1MTEUtWkEhKioKCkFob3JhIGVzdGFtb3MgbGlzdG9zIHBhcmEgZXhwbG9yYXIgbGEgZGF0YSB5IHZpc3VhbGl6YXJsYS4gCgojIFBhcnRlIDI6IEdyw6FmaWNvcyB5IFBhcmEgUXXDqSBTaXJ2ZW4KCkhheSBncsOhZmljb3MgcGFyYSB0b2RvLCBtw6FzIGRlIGxvcyBxdWUgbG9ncmFyZW1vcyBjdWJyaXIgZW4gZWwgdGllbXBvIHF1ZSB0ZW5lbW9zLiBMb3MgZ3LDoWZpY29zIHNlIGFkYXB0YW4sIG5vIHNvbG8gYSBsbyBxdWUgcXVpZXJlbiBtb3N0cmFyLCBzaW5vIGFsIHRpcG8geSBsYSBjYW50aWRhZCBkZSB2YXJpYWJsZXMgcXVlIHF1aWVyZW4gaW5jbHVpci4gSGF5IGRvcyB0aXBvcyBkZSB2YXJpYWJsZXMgcXVlIHZhbW9zIGEgY3VicmlyOgoKLSBDYXRlZ8OzcmljYXM6IHZhcmlhYmxlcyBxdWUgY2xhc2lmaWNhbiBhIGxhIHVuaWRhZCBkZSBhbsOhbGlzaXMgZW4gZGlmZXJlbnRlcyBjYXRlZ29yw61hcy4gTGFzIGNhdGVnb3LDrWFzIHB1ZWRlbiBzZXIgb3JkZW5hZGFzIChwb3IgZWouLCAxZXIgc2VtZXN0cmUsIDJkbyBzZW1lc3RyZSwgZXRjKSBvIG5vbWluYWxlcyAocG9yIGVqLiwgYXp1bCwgYmxhbmNvLCBuZWdybywgZXRjLikuIAotIENvbnRpbnVhczogdmFyaWFibGVzIHF1ZSBkZXNjcmliZW4gYSB1bmEgdmFyaWFibGUgYSBwYXJ0aXIgZGUgdW5hIG1lZGlkYWQgY29udGludWEgKHBvciBlai4sIGVkYWQsIGFsdHVyYSwgc2FsYXJpbykuIAoKVmFtb3MgYSBjb21lbnphciBjb24gZ3LDoWZpY29zIHF1ZSBkZXNjcmliZW4gKip1bmEqKiB2YXJpYWJsZSAodGFudG8gY2F0ZWfDs3JpY2EgY29tbyBjb250aW51YSksIHkgbHVlZ28gcGFzYXJlbW9zIGEgZ3LDoWZpY29zIHF1ZSBkZXNjcmliZW4gKipkb3MqKi4gU2kgYmllbiBoYXkgZ3LDoWZpY29zIHF1ZSBkZXNjcmliZW4gdHJlcyB2YXJpYWJsZXMsIGVzb3Mgbm8gbG9zIHZhbW9zIGEgY3VicmlyIGVuIGVzdGUgdGFsbGVyLiBOb3RlbiBxdWUgZGVzY3JpYmlyIHVuYSBvIGRvcyB2YXJpYWJsZXMgbm8gc2lnbmlmaWNhIHF1ZSDDum5pY2FtZW50ZSB2YW1vcyBhIHV0aWxpemFyIHVuYSBvIGRvcyB2YXJpYWJsZXMgcGFyYSBwcm9wb3JjaW9uYXIgaW5mb3JtYWNpw7NuIGFsIGdyw6FmaWNvLiAKCiMjIEdyw6FmaWNvcyBVbml2YXJpYWRvcwoKTG9zIGdyw6FmaWNvcyB1bml2YXJpYWRvcyBtdWVzdHJhbiBsYSBkaXN0cmlidWNpw7NuIGRlIGxhIGRhdGEgZGUgdW5hIHNvbGEgdmFyaWFibGUuIAoKIyMjIFZhcmlhYmxlcyBDYXRlZ8OzcmljYXMKCkxhIGRpc3RyaWJ1Y2nDs24gZGUgdW5hIHNvbGEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgcG9yIGxvIGdlbmVyYWwgc2UgbGEgZ3JhZmljYSBlbiB1biBncsOhZmljbyBkZSBiYXJyYXMuIFRhbWJpw6luIGVuIHVuICpwaWUgY2hhcnQqLCBwZXJvIGVzb3Mgc29uIGhvcnJpYmxlcyB5IG5vIHNpcnZlbiBwYXJhIG5hZGEgZW50b25jZXMgbWUgbmllZ28gYSBlbnNlw7FhcmxvLiAKCiMjIyMgR3LDoWZpY29zIGRlIEJhcnJhcwoKTGEgYmFzZSBkZSBkYXRvcyBgTWFycmlhZ2VgIGNvbnRpZW5lIHJlZ2lzdHJvcyBkZSBtYXRyaW1vbmlvcyBkZSA5OCBpbmRpdmlkdW9zIGRlIEFsYWJhbWEgKHVuaWRhZCBkZSBhbsOhbGlzaXMpLiBQcmltZXJvIHZhbW9zIGEgbW9zdHJhcyB1biBncsOhZmljbyBkZSBiYXJyYXMgcXVlIG11ZXN0cmEgbGEgZGlzdHJpYnVjacOzbiBkZSBsb3MgcGFydGljaXBhbnRlcyBwb3Igc2lnbm8gem9kaWFjYWwuIAoKYGBge3IgYmFycGxvdH0KbGlicmFyeShnZ3Bsb3QyKQpkYXRhKE1hcnJpYWdlLCBwYWNrYWdlID0gIm1vc2FpY0RhdGEiKQoKIyBEaXN0cmlidWNpw7NuIHBvciB6b2RpYWNvCk1hcnJpYWdlICU+JQogIGdncGxvdChhZXMoeCA9IHNpZ24pKSArIAogIGdlb21fYmFyKCkKYGBgCgpMYSBtYXlvcsOtYSBzb24gcGlzY2lzLCBvYnZpby4gCgpQb2RlbW9zIG1vZGlmaWNhciBlbCBgZmlsbGAgeSBlbCBgY29sb3JgIGRlIGNhZGEgYmFycmEsIGxvcyBgbGFiZWxzYCwgeSBlbCBgdGl0bGVgIGHDsWFkaWVuZG8gb3BjaW9uZXMgYSBsYSBmdW5jaW9uIGBnZW9tX2JhcmAuCgpgYGB7ciBiYXJwbG90Mn0KTWFycmlhZ2UgJT4lCiAgZ2dwbG90KGFlcyh4ID0gc2lnbikpICsgCiAgZ2VvbV9iYXIoZmlsbCA9ICJjb3JuZmxvd2VyYmx1ZSIsIAogICAgICAgICAgIGNvbG9yPSJibGFjayIsCiAgICAgICAgICAgYWxwaGEgPSAuNykgKwogIGxhYnMoeCA9ICJab2RpYWNvIiwgCiAgICAgICB5ID0gIkZyZWN1ZW5jaWEiLCAKICAgICAgIHRpdGxlID0gIkluZGl2aWR1b3MgcG9yIHpvZGlhY28iKQpgYGAKCkxhIGJhcnJhcyBwdWVkZW4gcmVwcmVzZW50YXIgcG9yY2VudGFqZXMgZW4gbHVnYXIgZGUgZnJlY3VlbmNpYXMuIFBhcmEgbG9zIGdyw6FmaWNvcyBkZSBiYXJyYXMsIGVsIGPDs2RpZ28gYGFlcyh4PXNpZ24pYCBlcyBkZSBoZWNobyB1biBhdGFqbyBwYXJhIGBhZXMoeCA9IHNpZ24sIHkgPSAuLmNvdW50Li4pYCwgZG9uZGUgYC4uY291bnQuLmAgZXMgdW5hIHZhcmlhYmxlIGVzcGVjaWFsIHF1ZSByZXByZXNlbnRhIGxhIGZyZWN1ZW5jaWEgZGUgY2FkYSBjYXRlZ29yw61hLiBQb2RlbW9zIGNhbGN1bGFyIGxvcyBwb3JjZW50YWplcyB5IG1hcGVhcmxvcyBhIG51ZXN0cmEgdmFyaWFibGUgeS4gU2luIGVtYmFyZ28sIHRhbWJpw6luIGRlYmVtb3MgY2FtYmlhciBlbCBnZW9tIGEgYGdlb21fY29sYCBvIHVuIGdlb20gcGFyYSBncmFmaWNhciBjb2x1bW5hcyAoYGdlb21fYmFyYCBlcyB1biBzdWJmdW5jacOzbiBkZSBgZ2VvbV9jb2xgKS4gCgpgYGB7ciBiYXJwbG90M30KTWFycmlhZ2UgJT4lCiAgbXV0YXRlKHRvdGFsX2Jhc2UgPSBuKCkpICU+JSAjIENhbGN1bGEgZWwgdG90YWwgZGUgb2JzZXJ2YWNpb25lcyBkZSBsYSBiYXNlCiAgZ3JvdXBfYnkoc2lnbikgJT4lCiAgbXV0YXRlKHRvdGFsX3NpZ25vID0gbigpLCAjIENhbGN1bGEgZWwgdG90YWwgZGUgb2JzZXJ2YWNpb25lcyBwb3Igem9kaWFjbwogICAgICAgICBwY3Rfc2lnbm8gPSB0b3RhbF9zaWduby90b3RhbF9iYXNlKSAlPiUgIyBDYWxjdWxhIGVsIHBvcmNlbnRhamUKICAjIENvbW8gYWhvcmEgbWkgdW5pZGFkIGRlIGFuYWxpc2lzIGVzIGVsIHNpZ25vLCBlbnRvbmNlcyB0ZW5lbW9zIHF1ZSBjYW1iaWFyIGxhIGJhc2UgZGUgZGF0b3MgYSBlc2EgdW5pZGFkOgogIGRpc3RpbmN0KHNpZ24sIC5rZWVwX2FsbD1UKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBzaWduLCB5ID0gcGN0X3NpZ25vKSkgKyAKICBnZW9tX2NvbChmaWxsID0gImNvcm5mbG93ZXJibHVlIiwgCiAgICAgICAgICAgY29sb3I9ImJsYWNrIiwKICAgICAgICAgICBhbHBoYSA9IC43KSArCiAgbGFicyh4ID0gIlpvZGlhY28iLCAKICAgICAgIHkgPSAiUG9yY2VudGFqZSIsIAogICAgICAgdGl0bGUgPSAiSW5kaXZpZHVvcyBwb3Igem9kaWFjbyIpICsKICAjIExlIGRlY2ltb3MgcXVlIHkgZXN0w6EgZW4gdW4gc2NhbGUgZGUgcG9yY2VudGFqZQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpIApgYGAKCk11Y2hhcyB2ZWNlcyB2YW4gYSBxdWVyZXIgb3JkZW5hciBzdXMgYmFycmFzIHBvciBmcmVjdWVuY2lhIChvIHBvcmNlbnRhamUpLiBQYXJhIGhhY2VyIGVzdG8gY29uIGVsIGdyw6FmaWNvIGFudGVyaW9yLCBwb2RlbW9zIHV0aWxpemFyIGxhIGZ1bmNpw7NuIGByZW9yZGVyYCB5IG9yZGVuYXIgbGFzIGNhdGVnb3LDrWFzIHBvciBmcmVjdWVuY2lhLiAKCmBgYHtyIHJlb3JkZXJ9Ck1hcnJpYWdlICU+JQogIG11dGF0ZSh0b3RhbF9iYXNlID0gbigpKSAlPiUgIyBDYWxjdWxhIGVsIHRvdGFsIGRlIG9ic2VydmFjaW9uZXMgZGUgbGEgYmFzZQogIGdyb3VwX2J5KHNpZ24pICU+JQogIG11dGF0ZSh0b3RhbF9zaWdubyA9IG4oKSwgIyBDYWxjdWxhIGVsIHRvdGFsIGRlIG9ic2VydmFjaW9uZXMgcG9yIHpvZGlhY28KICAgICAgICAgcGN0X3NpZ25vID0gdG90YWxfc2lnbm8vdG90YWxfYmFzZSkgJT4lICMgQ2FsY3VsYSBlbCBwb3JjZW50YWplCiAgIyBDb21vIGFob3JhIG1pIHVuaWRhZCBkZSBhbmFsaXNpcyBlcyBlbCBzaWdubywgZW50b25jZXMgdGVuZW1vcyBxdWUgY2FtYmlhciBsYSBiYXNlIGRlIGRhdG9zIGEgZXNhIHVuaWRhZDoKICBkaXN0aW5jdChzaWduLCAua2VlcF9hbGw9VCkgJT4lCiAgIyBPcmRlbmFtb3MgbGEgdmFyaWFiZWwgc2lnbiBlbiBmdW5jacOzbiBkZSBwY3Rfc2lnbm8KICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKHNpZ24scGN0X3NpZ25vKSwgeSA9IHBjdF9zaWdubykpICsgCiAgZ2VvbV9jb2woZmlsbCA9ICJjb3JuZmxvd2VyYmx1ZSIsIAogICAgICAgICAgIGNvbG9yPSJibGFjayIsCiAgICAgICAgICAgYWxwaGEgPSAuNykgKwogIGxhYnMoeCA9ICJab2RpYWNvIiwgCiAgICAgICB5ID0gIlBvcmNlbnRhamUiLCAKICAgICAgIHRpdGxlID0gIkluZGl2aWR1b3MgcG9yIHpvZGlhY28iKSArCiAgIyBMZSBkZWNpbW9zIHF1ZSB5IGVzdMOhIGVuIHVuIHNjYWxlIGRlIHBvcmNlbnRhamUKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSAKYGBgCgpvLCBzaSBxdWllcmVuIHF1ZSBkZXNjaWVuZGEsIGxlIHBvbmVtb3MgdW4gKiotKiogYSBsYSB2YXJpYWJsZSBwb3IgbGEgY3VhbCBlc3RhbW9zIG9yZGVuYW5kbzoKCmBgYHtyIHJlb3JkZXIyfQpNYXJyaWFnZSAlPiUKICBtdXRhdGUodG90YWxfYmFzZSA9IG4oKSkgJT4lICMgQ2FsY3VsYSBlbCB0b3RhbCBkZSBvYnNlcnZhY2lvbmVzIGRlIGxhIGJhc2UKICBncm91cF9ieShzaWduKSAlPiUKICBtdXRhdGUodG90YWxfc2lnbm8gPSBuKCksICMgQ2FsY3VsYSBlbCB0b3RhbCBkZSBvYnNlcnZhY2lvbmVzIHBvciB6b2RpYWNvCiAgICAgICAgIHBjdF9zaWdubyA9IHRvdGFsX3NpZ25vL3RvdGFsX2Jhc2UpICU+JSAjIENhbGN1bGEgZWwgcG9yY2VudGFqZQogICMgQ29tbyBhaG9yYSBtaSB1bmlkYWQgZGUgYW5hbGlzaXMgZXMgZWwgc2lnbm8sIGVudG9uY2VzIHRlbmVtb3MgcXVlIGNhbWJpYXIgbGEgYmFzZSBkZSBkYXRvcyBhIGVzYSB1bmlkYWQ6CiAgZGlzdGluY3Qoc2lnbiwgLmtlZXBfYWxsPVQpICU+JQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoc2lnbiwtcGN0X3NpZ25vKSwgeSA9IHBjdF9zaWdubykpICsgCiAgZ2VvbV9jb2woZmlsbCA9ICJjb3JuZmxvd2VyYmx1ZSIsIAogICAgICAgICAgIGNvbG9yPSJibGFjayIsCiAgICAgICAgICAgYWxwaGEgPSAuNykgKwogIGxhYnMoeCA9ICJab2RpYWNvIiwgCiAgICAgICB5ID0gIlBvcmNlbnRhamUiLCAKICAgICAgIHRpdGxlID0gIkluZGl2aWR1b3MgcG9yIHpvZGlhY28iKSArCiAgIyBMZSBkZWNpbW9zIHF1ZSB5IGVzdMOhIGVuIHVuIHNjYWxlIGRlIHBvcmNlbnRhamUKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSAKYGBgCgpTaSBxdWlyZW4gbcOhcyBpbmZvcm1hY2nDs24sIHBvZGVtb3MgYWdyZWdhciBlbCBwb3JjZW50YWplIGRlIGNhZGEgYmFycmEgdXRpbGl6YW5kbyBsYSBmdW5jacOzbiBgZ2VvbV90ZXh0YC4KCmBgYHtyIGdlb210ZXh0fQpNYXJyaWFnZSAlPiUKICAjIENhbGN1bGEgZWwgdG90YWwgZGUgb2JzZXJ2YWNpb25lcyBkZSBsYSBiYXNlCiAgbXV0YXRlKHRvdGFsX2Jhc2UgPSBuKCkpICU+JSAKICBncm91cF9ieShzaWduKSAlPiUKICAjIENhbGN1bGEgZWwgdG90YWwgZGUgb2JzZXJ2YWNpb25lcyBwb3Igem9kaWFjbwogIG11dGF0ZSh0b3RhbF9zaWdubyA9IG4oKSwgCiAgICAgICAgICMgQ2FsY3VsYSBlbCBwb3JjZW50YWplCiAgICAgICAgIHBjdF9zaWdubyA9IHRvdGFsX3NpZ25vL3RvdGFsX2Jhc2UsIAogICAgICAgICAjIExlIGRhbW9zIGZvcm1hdG8gYWwgbGFiZWwKICAgICAgICAgcGN0X2xhYmVsID0gcGFzdGUwKHJvdW5kKHBjdF9zaWdubyoxMDApLCAiJSIpKSAlPiUgCiAgIyBDb21vIGFob3JhIG1pIHVuaWRhZCBkZSBhbmFsaXNpcyBlcyBlbCBzaWdubywgZW50b25jZXMgdGVuZW1vcyBxdWUgY2FtYmlhciBsYSBiYXNlIGRlIGRhdG9zIGEgZXNhIHVuaWRhZDoKICBkaXN0aW5jdChzaWduLCAua2VlcF9hbGw9VCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihzaWduLC1wY3Rfc2lnbm8pLCB5ID0gcGN0X3NpZ25vKSkgKyAKICBnZW9tX2NvbChmaWxsID0gImNvcm5mbG93ZXJibHVlIiwgCiAgICAgICAgICAgY29sb3I9ImJsYWNrIiwKICAgICAgICAgICBhbHBoYSA9IC43KSArCiAgbGFicyh4ID0gIlpvZGlhY28iLCAKICAgICAgIHkgPSAiUG9yY2VudGFqZSIsIAogICAgICAgdGl0bGUgPSAiSW5kaXZpZHVvcyBwb3Igem9kaWFjbyIpICsKICAjIExlIGRlY2ltb3MgcXVlIHkgZXN0w6EgZW4gdW4gc2NhbGUgZGUgcG9yY2VudGFqZQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICAjIFNhY2Ftb3MgbG9zIGxhYmVscyBkZSBwY3Rfc2lnbm8uIFRhbWJpw6luIHBvZGVtb3MgYWp1c3RhciBsYSBhbHR1cmEgZGVsIHRleHRvIG9uIHJlbGFjacOzbiBhIGxhcyBiYXJyYXMgKG9wY2lvbmFsKQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwY3RfbGFiZWwpLCAKICAgICAgICAgICAgdmp1c3QgPSAtMC4yNSkgCmBgYAoKWWEgY2FzaS4gUGVybyBlc29zIG5vbWJyZXMgZGUgbGFzIHZhcmlhYmxlcyBzZSBzb2JyZXBvbmVuIHkgZXN0w6FuIGZlw61zaW1vcy4gVGVuZW1vcyBkb3Mgc29sdWNpb25lczogMSkgcG9kZW1vcyByb3RhciBsb3MgZWplcyB1dGlsaXphbmRvIGNvb3JkX2ZsaXAgbyAyKSBwb2RlbW9zIHJvdGFyIGxvcyBub21icmVzLiBWZWFtb3MgbGFzIGRvcyBzb2x1Y2lvbmVzOgoKMSkKCmBgYHtyIGNvb3JkZmxpcH0KTWFycmlhZ2UgJT4lCiAgIyBDYWxjdWxhIGVsIHRvdGFsIGRlIG9ic2VydmFjaW9uZXMgZGUgbGEgYmFzZQogIG11dGF0ZSh0b3RhbF9iYXNlID0gbigpKSAlPiUgCiAgZ3JvdXBfYnkoc2lnbikgJT4lCiAgIyBDYWxjdWxhIGVsIHRvdGFsIGRlIG9ic2VydmFjaW9uZXMgcG9yIHpvZGlhY28KICBtdXRhdGUodG90YWxfc2lnbm8gPSBuKCksIAogICAgICAgICAjIENhbGN1bGEgZWwgcG9yY2VudGFqZQogICAgICAgICBwY3Rfc2lnbm8gPSB0b3RhbF9zaWduby90b3RhbF9iYXNlLCAKICAgICAgICAgIyBMZSBkYW1vcyBmb3JtYXRvIGFsIGxhYmVsCiAgICAgICAgIHBjdF9sYWJlbCA9IHBhc3RlMChyb3VuZChwY3Rfc2lnbm8qMTAwKSwgIiUiKSkgJT4lIAogICMgQ29tbyBhaG9yYSBtaSB1bmlkYWQgZGUgYW5hbGlzaXMgZXMgZWwgc2lnbm8sIGVudG9uY2VzIHRlbmVtb3MgcXVlIGNhbWJpYXIgbGEgYmFzZSBkZSBkYXRvcyBhIGVzYSB1bmlkYWQ6CiAgZGlzdGluY3Qoc2lnbiwgLmtlZXBfYWxsPVQpICU+JQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoc2lnbiwtcGN0X3NpZ25vKSwgeSA9IHBjdF9zaWdubykpICsgCiAgZ2VvbV9jb2woZmlsbCA9ICJjb3JuZmxvd2VyYmx1ZSIsIAogICAgICAgICAgIGNvbG9yPSJibGFjayIsCiAgICAgICAgICAgYWxwaGEgPSAuNykgKwogIGxhYnMoeCA9ICJab2RpYWNvIiwgCiAgICAgICB5ID0gIlBvcmNlbnRhamUiLCAKICAgICAgIHRpdGxlID0gIkluZGl2aWR1b3MgcG9yIHpvZGlhY28iKSArCiAgIyBMZSBkZWNpbW9zIHF1ZSB5IGVzdMOhIGVuIHVuIHNjYWxlIGRlIHBvcmNlbnRhamUKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArCiAgIyBTYWNhbW9zIGxvcyBsYWJlbHMgZGUgcGN0X3NpZ25vLiBUYW1iacOpbiBwb2RlbW9zIGFqdXN0YXIgbGEgcG9zaWNpw7NuIGhvcml6b250YWwgZGVsIHRleHRvIG9uIHJlbGFjacOzbiBhIGxhcyBiYXJyYXMgKG9wY2lvbmFsKQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwY3RfbGFiZWwpLCBoanVzdCA9IC0uMTUpICsKICAjIFJvdGFtb3MgbG9zIGVqZXMKICBjb29yZF9mbGlwKCkKYGBgCgoyKQoKYGBge3IgY29vcmRmbGlwMn0KTWFycmlhZ2UgJT4lCiAgIyBDYWxjdWxhIGVsIHRvdGFsIGRlIG9ic2VydmFjaW9uZXMgZGUgbGEgYmFzZQogIG11dGF0ZSh0b3RhbF9iYXNlID0gbigpKSAlPiUgCiAgZ3JvdXBfYnkoc2lnbikgJT4lCiAgIyBDYWxjdWxhIGVsIHRvdGFsIGRlIG9ic2VydmFjaW9uZXMgcG9yIHpvZGlhY28KICBtdXRhdGUodG90YWxfc2lnbm8gPSBuKCksIAogICAgICAgICAjIENhbGN1bGEgZWwgcG9yY2VudGFqZQogICAgICAgICBwY3Rfc2lnbm8gPSB0b3RhbF9zaWduby90b3RhbF9iYXNlLCAKICAgICAgICAgIyBMZSBkYW1vcyBmb3JtYXRvIGFsIGxhYmVsCiAgICAgICAgIHBjdF9sYWJlbCA9IHBhc3RlMChyb3VuZChwY3Rfc2lnbm8qMTAwKSwgIiUiKSkgJT4lIAogICMgQ29tbyBhaG9yYSBtaSB1bmlkYWQgZGUgYW5hbGlzaXMgZXMgZWwgc2lnbm8sIGVudG9uY2VzIHRlbmVtb3MgcXVlIGNhbWJpYXIgbGEgYmFzZSBkZSBkYXRvcyBhIGVzYSB1bmlkYWQ6CiAgZGlzdGluY3Qoc2lnbiwgLmtlZXBfYWxsPVQpICU+JQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoc2lnbiwtcGN0X3NpZ25vKSwgeSA9IHBjdF9zaWdubykpICsgCiAgZ2VvbV9jb2woZmlsbCA9ICJjb3JuZmxvd2VyYmx1ZSIsIAogICAgICAgICAgIGNvbG9yPSJibGFjayIsCiAgICAgICAgICAgYWxwaGEgPSAuNykgKwogIGxhYnMoeCA9ICJab2RpYWNvIiwgCiAgICAgICB5ID0gIlBvcmNlbnRhamUiLCAKICAgICAgIHRpdGxlID0gIkluZGl2aWR1b3MgcG9yIHpvZGlhY28iKSArCiAgIyBMZSBkZWNpbW9zIHF1ZSB5IGVzdMOhIGVuIHVuIHNjYWxlIGRlIHBvcmNlbnRhamUKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArCiAgIyBTYWNhbW9zIGxvcyBsYWJlbHMgZGUgcGN0X3NpZ25vLiBUYW1iacOpbiBwb2RlbW9zIGFqdXN0YXIgbGEgcG9zaWNpw7NuIGhvcml6b250YWwgZGVsIHRleHRvIG9uIHJlbGFjacOzbiBhIGxhcyBiYXJyYXMgKG9wY2lvbmFsKQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwY3RfbGFiZWwpLCAKICAgICAgICAgICAgdmp1c3QgPSAtLjI1KSArCiAgIyBSb3RhbW9zIGVsIHRleHRvIGVsIGVqZSB4CiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSkKYGBgCgpQb25pZW5kbyBqdW50byB0b2RvIGxvIHF1ZSBhcHJlbmRpbW9zIGFudGVzIHBvZGVtb3MgaGFjZXIgdW4gZ3LDoWZpY28gcXVlIHNlIHZlIGFsZ28gYXPDrToKCmBgYHtyIGJhcmZpbmFsfQpNYXJyaWFnZSAlPiUKICAjIENhbGN1bGEgZWwgdG90YWwgZGUgb2JzZXJ2YWNpb25lcyBkZSBsYSBiYXNlCiAgbXV0YXRlKHRvdGFsX2Jhc2UgPSBuKCkpICU+JSAKICBncm91cF9ieShzaWduKSAlPiUKICAjIENhbGN1bGEgZWwgdG90YWwgZGUgb2JzZXJ2YWNpb25lcyBwb3Igem9kaWFjbwogIG11dGF0ZSh0b3RhbF9zaWdubyA9IG4oKSwgCiAgICAgICAgICMgQ2FsY3VsYSBlbCBwb3JjZW50YWplCiAgICAgICAgIHBjdF9zaWdubyA9IHRvdGFsX3NpZ25vL3RvdGFsX2Jhc2UsIAogICAgICAgICAjIExlIGRhbW9zIGZvcm1hdG8gYWwgbGFiZWwKICAgICAgICAgcGN0X2xhYmVsID0gcGFzdGUwKHJvdW5kKHBjdF9zaWdubyoxMDApLCAiJSIpKSAlPiUgCiAgIyBDb21vIGFob3JhIG1pIHVuaWRhZCBkZSBhbmFsaXNpcyBlcyBlbCBzaWdubywgZW50b25jZXMgdGVuZW1vcyBxdWUgY2FtYmlhciBsYSBiYXNlIGRlIGRhdG9zIGEgZXNhIHVuaWRhZDoKICBkaXN0aW5jdChzaWduLCAua2VlcF9hbGw9VCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihzaWduLC1wY3Rfc2lnbm8pLCB5ID0gcGN0X3NpZ25vKSkgKyAKICBnZW9tX2NvbChmaWxsID0gImNvcm5mbG93ZXJibHVlIiwgCiAgICAgICAgICAgY29sb3I9ImJsYWNrIiwKICAgICAgICAgICBhbHBoYSA9IC43KSArCiAgIyBMZSBkZWNpbW9zIHF1ZSB5IGVzdMOhIGVuIHVuIHNjYWxlIGRlIHBvcmNlbnRhamUKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIC4yLCAuMDUpLAogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwuMikpICsKICAjIFNhY2Ftb3MgbG9zIGxhYmVscyBkZSBwY3Rfc2lnbm8uIFRhbWJpw6luIHBvZGVtb3MgYWp1c3RhciBsYSBwb3NpY2nDs24gaG9yaXpvbnRhbCBkZWwgdGV4dG8gb24gcmVsYWNpw7NuIGEgbGFzIGJhcnJhcyAob3BjaW9uYWwpCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBjdF9sYWJlbCksIAogICAgICAgICAgICB2anVzdCA9IC0uMjUpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICAjIFJvdGFtb3MgZWwgdGV4dG8gZWwgZWplIHgKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEpKSArCiAgbGFicyh0aXRsZSA9ICJQb3JjZW50YWplIGRlIE1hdHJpbW9uaW9zIHBvciBab2RpYWNvIiwKICAgICAgIHN1YnRpdGxlID0gIlJlZ2lzdHJvIGRlbCBlc3RhZG8gZGUgQWxhYmFtYSIsCiAgICAgICAjIGNhcHRpb24gPSAiZnVlbnRlOiBodHRwOi8vbW9zYWljLXdlYi5vcmcvIiwKICAgICAgIHggPSAiIiwgCiAgICAgICB5ID0gIlBvcmNlbnRhamUiKSAKYGBgCgojIyMgVmFyaWFibGVzIENvbnRpbnVhcwoKTGEgZGlzdHJpYnVjacOzbiBkZSB1bmEgc29sYSB2YXJpYWJsZSBjb250aW51YSBwb3IgbG8gZ2VuZXJhbCBzZSBsYSBncmFmaWNhIGVuIHVuIGhpc3RvZ3JhbWEgbyB1bmEgZ3LDoWZpY28gZGUgZGVuc2lkYWQuIAoKIyMjIyBIaXN0b2dyYW1hcwoKVXRpbGl6YW5kbyBsYSBtaXNtYSBiYXNlIGRlIGRhdG9zIGRlbCBlamVtcGxvIGFudGVyaW9yLCB2YW1vcyBhIGdyYWZpY2FyIGVsIGhpc3RvZ3JhbWEgZGUgbGEgZWRhZCBkZSBsb3MgcGFydGljaXBhbnRlcy4KCmBgYHtyIGhpc3RvfQpNYXJyaWFnZSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBhZ2UpKSArIAogIGdlb21faGlzdG9ncmFtKCkgKyAKICBsYWJzKHRpdGxlID0gIlBhcnRpY2lwYW50ZXMgcG9yIGVkYWQiLAogICAgICAgeCA9ICJFZGFkIikKYGBgCgpMYSBtYXlvcsOtYSBkZSBsb3MgcGFydGljaXBhbnRlcyBwYXJlY2VuIGVzdGFyIGVuIHN1cyB2ZWludGVzIHkgb3RybyBncnVwbyBlbiBzdXMgY3VhcmVudGEgY29uIHVubyBjdWFudG9zIHJ1Y29zIHJ1Y29zIGFsIGZpbmFsLiBFc3RvIGVzIHVuYSBkaXN0cmlidWNpw7NuIG11bHRpbW9kYWwuIAoKUG9kZW1vcyBtb2RpZmljYXIgbG9zIGNvbG9yZXMgdXRpbGl6YW50byBgZmlsbGAgeSBgY29sb3JgIChwYXJhIGVsIGJvcmRlKS4gCgpgYGB7ciBoaXN0bzJ9Ck1hcnJpYWdlICU+JQogIGdncGxvdChhZXMoeCA9IGFnZSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJwZWFjaHB1ZmYiLCAKICAgICAgICAgICBjb2xvcj0icGVydSIsCiAgICAgICAgICAgYWxwaGEgPSAuNykgKyAKICBsYWJzKHRpdGxlID0gIlBhcnRpY2lwYW50ZXMgcG9yIGVkYWQiLAogICAgICAgeCA9ICJFZGFkIikKYGBgCgpVbmEgZGUgbGFzIG9wY2lvbmVzIG3DoXMgaW1wb3J0YW50ZXMgZGUgbG9zIGhpc3RvZ3JhbWFzIHNvbiBsb3MgYGJpbnNgIHF1ZSBjb250cm9sYSBlbCBuw7ptZXJvIGRlIGNvbnRlbmVkb3JlcyBlbiBsYXMgY3VhbGVzIGxhIHZhcmlhYmxlIG7Dum1lcmljYSBlc3TDoSBkaXZpZGlkYSAoaS5lLiwgZWwgbsO6bWVybyBkZSBiYXJyYXMgZW4gZWwgZ3LDoWZpY28pLiBFbCBkZWZhdWx0IGVzIDMwLCBwZXJvIGVzIGJ1ZW5vIGludGVudGFyIGNvbiBuw7ptZXJvcyBtw6FzIGdyYW5kZXMgbyBtw6FzIHBlcXVlw7FvcyBwYXJhIHRlbmVyIHVuYSBtZWpvciByZXByZXNlbnRhY2nDs24gZGUgbGEgZm9ybWEgZGUgbGEgZGlzdHJpYnVjacOzbi4gCgpgYGB7ciBoaXN0bzN9Ck1hcnJpYWdlICU+JQogIGdncGxvdChhZXMoeCA9IGFnZSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJwZWFjaHB1ZmYiLCAKICAgICAgICAgICBjb2xvcj0icGVydSIsCiAgICAgICAgICAgYWxwaGEgPSAuNywKICAgICAgICAgICBiaW5zID0gMjApICsgCiAgbGFicyh0aXRsZSA9ICJQYXJ0aWNpcGFudGVzIHBvciBlZGFkIiwKICAgICAgIHN1YnRpdGxlID0gImJpbnMgPSAyMCIsCiAgICAgICB4ID0gIkVkYWQiKQpgYGAKClRhbWJpw6luIHB1ZWRlbiBlc3BlY2lmaWNhciBlbCBiaW53aWR0aCwgbGEgYW5jaG8gZGUgbG9zIGNvbnRlbmVkb3JlcyByZXByZXNlbnRhZG9zIHBvciBsYXMgYmFycmFzLgoKYGBge3IgaGlzdG80fQpNYXJyaWFnZSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBhZ2UpKSArIAogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAicGVhY2hwdWZmIiwgCiAgICAgICAgICAgY29sb3I9InBlcnUiLAogICAgICAgICAgIGFscGhhID0gLjcsCiAgICAgICAgICAgYmlud2lkdGggPSAzKSArIAogIGxhYnModGl0bGUgPSAiUGFydGljaXBhbnRlcyBwb3IgZWRhZCIsCiAgICAgICBzdWJ0aXRsZSA9ICJiaW53aWR0aCA9IDMiLAogICAgICAgeCA9ICJFZGFkIikKYGBgCgojIyMjIERlbnNpdHkgUGxvdAoKVW5hIG1hbmVyYSBhbHRlcm5hdGl2YSBhIHVuIGhpc3RvZ3JhbWEgZXMgdW4ga2VybmVsIGRlbnNpdHkgcGxvdC4gTGEgZXN0aW1hY2nDs24gZGVsIGtlcm5lbCBkZW5zaXR5IGVzIHVuIG3DqXRvZG8gbm8gcGFyYW3DqXRyaWNvIHBhcmEgZXN0aW1hciBsYSBmdW5jacOzbiBkZSBwcm9iYWJpbGlkYWQgZGUgbGEgZGVuc2lkYWQgZGUgdW5hIHZhcmlhYmxlIGNvbnRpbnVhIGFsZWF0b3JpYS4gRW4gZsOhY2lsLCBlc3RhbW9zIGludGVudGFuZG8gZ3JhZmljYXIgdW4gaGlzdG9ncmFtYSBjb250aW51byBkb25kZSBlbCDDoXJlYSBiYWpvIGxhIGN1cnZhIGVzIGlndWFsIGEgdW5vLgoKYGBge3IgZGVuc2l0eX0KTWFycmlhZ2UgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYWdlKSkgKyAKICBnZW9tX2RlbnNpdHkoKSArIAogIGxhYnModGl0bGUgPSAiUGFydGljaXBhbnRlcyBwb3IgZWRhZCIpCmBgYAoKQWwgaWd1YWwgcXVlIGNvbiBsb3MgaGlzdG9ncmFtYXMsIHBvZGVtb3MgY2FtYmlhciBlbCBgY29sb3JgIHkgZWwgYGZpbGxgLiBUYW1iacOpbiBwb2RlbW9zIG1vZGlmaWNhciBlbCBgYndgIG8gZWwgdMOpcm1pbm8gcXVlIGRldGVybWluYSBjdcOhbnRvIGRlIGxhIGRhdGEgdG9tYSBhIGxhIHZleiBwYXJhIGhhY2VyIGxhcyBjdXJ2YXMuCgpgYGB7ciBkZW5zaXR5Mn0KTWFycmlhZ2UgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYWdlKSkgKyAKICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJwZWFjaHB1ZmYiLCAKICAgICAgICAgICBjb2xvcj0icGVydSIsCiAgICAgICAgICAgYWxwaGEgPSAuNywKICAgICAgICAgICBidyA9IDIpICsgCiAgbGFicyh0aXRsZSA9ICJQYXJ0aWNpcGFudGVzIHBvciBlZGFkIiwKICAgICAgIHN1YnRpdGxlID0gImJhbmR3aWR0aCA9IDIiLAogICAgICAgeCA9ICJFZGFkIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCiMjIEdyw6FmaWNvcyBCaXZhcmlhZG9zCgpMb3MgZ3LDoWZpY29zIGJpdmFyaWFkb3MgbXVlc3RyYW4gbGEgcmVsYWNpw7NuIGVudHJlIGRvcyB2YXJpYWJsZXMuIEVsIHRpcG8gZGUgZ3LDoWZpY28gZGVwZW5kZXLDoSBkZWwgdGlwbyBkZSB2YXJpYWJsZXMgKGNhdGVnw7NyaWNhcyBvIGNvbnRpbnVhcykuIAoKCiMjIyBDYXRlZ8OzcmljYSB2cy4gQ2F0ZWfDs3JpY2EgCgpMbyBtw6FzIGNvbcO6biBwYXJhIHJlcHJlc2VudGFyIGxhIHJlbGFjacOzbiBlbnRyZSBkb3MgdmFyaWFibGVzIGNhdGVnw7NyaWNhcyBlcyBlbiBncsOhZmljb3MgZGUgYmFycmFzLiBQb3IgZWplbXBsbywgcG9kZW1vcyBldmFsdWFyIGxhIHJlbGFjacOzbiBlbnRyZSBlbCBnw6luZXJvIHkgbGEgcG9zaWNpw7NuIGFjYWTDqW1pY2EgZW4gdW5hIGJhc2UgZGUgZGF0b3MgcXVlIGRlc2NyaWJlIGxvcyBzYWxhcmlvcyBkZSAzOTcgcHJvZmVzb3JlcyB1bml2ZXJzaXRhcmlvICgyMDA5KS4gCgpWZWFtb3MgdW4gc3RhY2tlZCBiYXIgcGxvdDoKCmBgYHtyIHJhbmtzZXh9CmRhdGEoU2FsYXJpZXMsIHBhY2thZ2U9ImNhckRhdGEiKQoKU2FsYXJpZXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcmFuaywgZmlsbCA9IHNleCkpICsgCiAgZ2VvbV9iYXIoKSAKYGBgCgpVbiBwb2NvIGRpZsOtY2lsIGRlIHZlci4gUG9kZW1vcyBwb25lcmxhcyB1bmEganVudG8gYSBsYSBvdHJhLiAKCmBgYHtyIHJhbmtzZXgyfQpTYWxhcmllcyAlPiUKICBnZ3Bsb3QoYWVzKHggPSByYW5rLCBmaWxsID0gc2V4KSkgKyAKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpIApgYGAKClRhbWJpw6luIHBvZGVtb3MgaGFjZXIgcXVlIGNhZGEgYmFycmEgcmVwcmVzZW50ZSBlbCAxMDAlLiAKCmBgYHtyIHJhbmtzZXgzfQpTYWxhcmllcyAlPiUKICBnZ3Bsb3QoYWVzKHggPSByYW5rLCBmaWxsID0gc2V4KSkgKyAKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogIGxhYnMoeSA9ICJQcm9wb3JjacOzbiIpCmBgYAoKRXN0ZSDDumx0aW1vIGdyw6FmaWNvIGVzIHBhcnRpY3VsYXJtZW50ZSDDunRpbCBzaSBzdSBvYmpldGl2byBlcyBjb21wYXJhciBlbCBwb3JjZW50YWplIGRlIHVuYSBjYXRlZ29yw61hIGVuIGZ1bmNpw7NuIGRlIG90cmEgdmFyaWFibGUuIFBvciBlamVtcGxvLiBsYSBwcm9wb3JjacOzbiBkZSBtdWplcmVzIHNlIHJlZHVjZSBhIG1lZGlkYSBxdWUgYXVtZW50YSBsYSBwb3NpY2nDs24gZGUgQXNzaXN0YW50IFByb2Zlc3NvciwgYSBBc3NvY2lhdGUgUHJvZmVzc29yLCBhIFByb2Zlc3Nvci4gCgpDb21vIGVuIHRvZG8sIHBvZGVtb3MgcGVyc29uYWxpemFyIHkgZW1iZWxsZWNlciBsb3MgZ3LDoWZpY29zLiAKCmBgYHtyIHJhbmtzZXhmaW5hbH0KU2FsYXJpZXMgJT4lCiAgbXV0YXRlKHJhbmsgPSByZWNvZGVfZmFjdG9yKHJhbmssCiAgICAgICAgICAgICAgICAgICAgICAgQXNzdFByb2YgPSAiQXNzaXN0YW50IFByb2Zlc3NvciIsIAogICAgICAgICAgICAgICAgICAgICAgIEFzc29jUHJvZiA9ICJBc3NvY2lhdGUgUHJvZmVzc29yIiwKICAgICAgICAgICAgICAgICAgICAgICBQcm9mID0gIlByb2Zlc3NvciIpKSAlPiUKICBncm91cF9ieShyYW5rKSAlPiUKICBtdXRhdGUodG90YWxfcmFuaz0gbigpKSAlPiUKICBncm91cF9ieShyYW5rLHNleCkgJT4lCiAgbXV0YXRlKHRvdGFsX3Jhbmtfc2V4ID0gbigpLAogICAgICAgICBwY3RfcmFua19zZXggPSB0b3RhbF9yYW5rX3NleC90b3RhbF9yYW5rLAogICAgICAgICBsYmwgPSBzY2FsZXM6OnBlcmNlbnQocGN0X3Jhbmtfc2V4KSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdncGxvdChhZXMoeCA9IHJhbmssZmlsbCA9IHNleCkpICsgCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEsIC4yKSwgCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbj0iRSIsYWxwaGEgPSAwLjUpICsKICBsYWJzKHggPSAiUG9zaWNpw7NuIiwgCiAgICAgICBmaWxsID0gIkfDqW5lcm8iLAogICAgICAgdGl0bGUgPSAiUG9zaWNpw7NuIHBvciBHw6luZXJvIiwKICAgICAgIHkgPSAiIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCiMjIyBDb250aW51YXMgdnMuIENvbnRpbnVhcyAKCkxhIHJlbGFjacOzbiBlbnRyZSBkb3MgdmFyaWFibGVzIGNvbnRpbnVhcyBwb3IgbG8gZ2VuZXJhbCBlcyByZXByZXNlbnRhZGEgcG9yIHVuIHNjYXR0ZXIgcGxvdCBvIHBvciB1bmEgZ3LDoWZpY28gZGUgbMOtbmVhLiBDb21lbmNlbW9zIHBvciBlbCBwcmltZXJvLiAgCgojIyMjIFNjYXR0ZXIgUGxvdAoKRWwgc2NhdHRlciBwbG90IGVzIHVuIGdyw6FmaWNvIGRvbmRlIHNlIHJlcHJlc2VudGEgY2FkYSB2YWlyYWJsZSBlbiB1biBlamUuIFBvciBlamVtcGxvLCBwb2RlbW9zIHV0aWxpemFyIGxhIG1pc21hIGJhc2UgZGUgZGF0b3MgYW50ZXJpb3IgeSBncmFmaWNhciBleHBlcmllbmNpYSAoYHlycy5zaW5jZS5waGRgKSB2cy4gc2FsYXJpbyBhY2Fkw6ltaWNvIChgc2FsYXJ5YCkgcGFyYSBwcm9mZXNvcmVzIHVuaXZlcnNpdGFyaW9zLiAKCmBgYHtyIHNjYXR0ZXJwbG90fQojIHNjYXR0ZXJwbG90IHNpbXBsZQpTYWxhcmllcyAlPiUKICBnZ3Bsb3QoYWVzKHggPSB5cnMuc2luY2UucGhkLCAKICAgICAgICAgICB5ID0gc2FsYXJ5KSkgKwogIGdlb21fcG9pbnQoKQpgYGAKCmxhcyBvcHRpb25lcyBkZSBgZ2VvbV9wb2ludGAgcHVlZGVuIHNlciBjYW1iaWFkYXM6CgotIGBjb2xvcmA6IGNvbG9yIGRlbCBwdW50bwotIGBzaXplYDogdGFtYcOxbyBkZWwgcHVudG8KLSBgc2hhcGVgOiBmb3JtYSBkZWwgcHVudG8KLSBgYWxwaGFgIDogdHJhbnNwYXJlbmNpYSBkZWwgcHVudG8KCkNvbW8gYXByZW5kaW1vcyBhbnRlcywgbGFzIGZ1bmNpb25lcyBgc2NhbGVfeF9jb250aW51b3VzYCB5IGBzY2FsZV95X2NvbnRpbnVvdXNgIGNvbnRyb2xhbiBsYXMgZXNjYWxhcyBkZWwgZWplIHggeSBlbCBlamUgeS4gCgpDb21vIHNpZW1wcmUsIHVzYW1vcyBlc3RhcyBmdW5jaW9uZXMgcGFyYSBoYWNlciBncsOhZmljb3MgbcOhcyBhdHJhY3Rpdm9zLiAKCmBgYHtyIHNjYXR0ZXJwbG90Mn0KIyBzY2F0dGVycGxvdCBzaW1wbGUKU2FsYXJpZXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0geXJzLnNpbmNlLnBoZCwgCiAgICAgICAgICAgeSA9IHNhbGFyeSkpICsKICBnZW9tX3BvaW50KGNvbG9yPSJwZWFjaHB1ZmYiLCAKICAgICAgICAgICAgIHNpemUgPSAyLCAKICAgICAgICAgICAgIGFscGhhPS43KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVsID0gc2NhbGVzOjpkb2xsYXIsIAogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDUwMDAwLCAyNTAwMDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA2MCwgMTApLCAKICAgICAgICAgICAgICAgICAgICAgbGltaXRzPWMoMCwgNjApKSArIAogIGxhYnMoeCA9ICJBw7FvcyBkZXNkZSBlbCBQaEQiLAogICAgICAgeSA9ICIiLAogICAgICAgdGl0bGUgPSAiRXhwZXJpZW5jaWEgdnMuIFNhbGFyaW8iLAogICAgICAgc3VidGl0bGUgPSAiU2FsYXJpbyBkZSA5IG1lc2VzIGVudHJlIDIwMDggeSAyMDA5IikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKClBvZGVtb3MgcmVzdW1pciBsYSByZWxhY2nDs24gcXVlIHNlIG11ZXN0cmEgZW4gZWwgc2NhdHRlcnBsb3QgdXRpbGl6YW5kbyB1biBiZXN0IGZpdCBsaW5lLiBIYXkgdmFyaWFzIG1hbmVyYXMgZGUgaGFjZXIgbGEgbMOtbmVhIChkZSByZXByZXNlbnRhciBsYSByZWxhY2nDs24pOiBsaW5lYWwsIHBvbGlub21pYWwsIG8gbm8gcGFyYW3DqXRyaWNhIChsb2VzcykuIFBvciBkZWZhdWx0LCBlbCBpbnRlcnZhbG8gZGUgY29uZmlhbnphIG1vc3RyYWRvIGVzIGRlbCA5NSUuIAoKYGBge3IgYmVzdGZpdGxpbmV9CiMgc2NhdHRlcnBsb3QgbGluZWFsClNhbGFyaWVzICU+JQogIGdncGxvdChhZXMoeCA9IHlycy5zaW5jZS5waGQsIAogICAgICAgICAgIHkgPSBzYWxhcnkpKSArCiAgZ2VvbV9wb2ludChjb2xvcj0icGVhY2hwdWZmIiwgCiAgICAgICAgICAgICBzaXplID0gMiwgCiAgICAgICAgICAgICBhbHBoYT0uNykgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsCiAgICAgICAgICAgICAgY29sb3IgPSAicGVydSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWwgPSBzY2FsZXM6OmRvbGxhciwgCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoNTAwMDAsIDI1MDAwMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDYwLCAxMCksIAogICAgICAgICAgICAgICAgICAgICBsaW1pdHM9YygwLCA2MCkpICsgCiAgbGFicyh4ID0gIkHDsW9zIGRlc2RlIGVsIFBoRCIsCiAgICAgICB5ID0gIiIsCiAgICAgICB0aXRsZSA9ICJFeHBlcmllbmNpYSB2cy4gU2FsYXJpbyIsCiAgICAgICBzdWJ0aXRsZSA9ICJTYWxhcmlvIGRlIDkgbWVzZXMgZW50cmUgMjAwOCB5IDIwMDkiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKQ2xhcmFtZW50ZSwgYSBtZWRpZGEgcXVlIGF1bWVudGEgbGEgZXhwZXJpZW5jaWEgdGFtYmnDqW4gYXVtZW50YSBlbCBzYWxhcmlvLiBTaW4gZW1iYXJnbywgcGFyZWNlIHF1ZSBoYXkgdW4gY2HDrWRhIGp1c3RvIGFsIGZpbmFsLiBMb3MgcHJvZmVzb3JlcyBjb24gbXVjaGEgZXhwZXJpZW5jaWEgdGllbmVuIG1lbm9yZXMgc2FsYXJpb3MuIFVuYSBsw61uZWEgcmVjdGEgbm8gY2FwdHVyYSBlc3RlIGVmZWN0byBuby1saW5lYWwuIFVuYSBjdXJ2YSBjYXB0dXJhIG1lam9yIGVzdGUgZWZlY3RvLiAKClVuIHBvbGlub21pbyBkaWJ1amEgdW5hIGN1cnZhIGNvbiBsYSBzaWd1aWVudGUgZm9ybWE6CgokJFxoYXR7eX0gPSBcYmV0YV8wICsgXGJldGFfMXggKyBcYmV0YV8yeF4yICsgXGJldGFfM3heMysuLi4kJAoKUG9yIGxvIGdlbmVyYWwsIHVuYSBmw7NybXVsYSBjdWFkcsOhdGljYSAodW5hIGN1cnZhKSBvIGPDumJpY2EgKGRvcyBjdXJ2YXMpIHNlIHV0aWxpemEuIFJhcmEgdmV6IHNlIHV0aWxpemEgdW5hIGN1cnZhIGRlIHVuIHBvbGlub21pbyBkZSBvcmRlbiBtYXlvciBhIDMuIFV0aWxpemFuZG8gdW5hIGbDs3JtdWxhIGN1YWRyw6F0aWNhIG9idGVuZW1vcyBlbCBzaWd1aWVudGUgZ3LDoWZpY286CgoKYGBge3IgYmVzdGZpdGxpbmUyfQojIHNjYXR0ZXJwbG90IGN1YWRyw6F0aWNvClNhbGFyaWVzICU+JQogIGdncGxvdChhZXMoeCA9IHlycy5zaW5jZS5waGQsIAogICAgICAgICAgIHkgPSBzYWxhcnkpKSArCiAgZ2VvbV9wb2ludChjb2xvcj0icGVhY2hwdWZmIiwgCiAgICAgICAgICAgICBzaXplID0gMiwgCiAgICAgICAgICAgICBhbHBoYT0uNykgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsCiAgICAgICAgICAgICAgZm9ybXVsYSA9IHkgfiBwb2x5KHgsIDIpLCAKICAgICAgICAgICAgICBjb2xvciA9ICJwZXJ1IikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbCA9IHNjYWxlczo6ZG9sbGFyLCAKICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYyg1MDAwMCwgMjUwMDAwKSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgNjAsIDEwKSwgCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cz1jKDAsIDYwKSkgKyAKICBsYWJzKHggPSAiQcOxb3MgZGVzZGUgZWwgUGhEIiwKICAgICAgIHkgPSAiIiwKICAgICAgIHRpdGxlID0gIkV4cGVyaWVuY2lhIHZzLiBTYWxhcmlvIiwKICAgICAgIHN1YnRpdGxlID0gIlNhbGFyaW8gZGUgOSBtZXNlcyBlbnRyZSAyMDA4IHkgMjAwOSIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgojIyMjIEdyw6FmaWNvIGRlIEzDrW5lYSAKCkN1YW5kbyB1bmEgZGUgbGFzIGRvcyB2YXJpYWJsZXMgcmVwcmVzZW50YSB0aWVtcG8sIHVuIGdyw6FmaWNvIGRlIGzDrW5lYSBwdWVkZSBzZXIgdW5hIG1hbmVyYSBlZmVjdGl2YSBkZSBtb3N0cmFyIGxhIHJlbGFjacOzbi4gUG9yIGVqZW1wbG8sIGVsIGPDs2RpZ28gYSBjb250aW51YWNpw7NuIG11ZXN0cmEgbGEgcmVsYWNpw7NuIGVudHJlIHRpZW1wbyAoYHllYXJgKSB5IGVsIHByb21lZGlvIGRlIGV4cGVjdGF0aXZhIGRlIHZpZGEgKGBsaWZlRXhwX21lYW5gKSBlbiBsYXMgQW1lcsOtY2FzIGVudHJlIDE5NTIgeSAyMDA3LiBMYSBkYXRhIHZpZW5lIGRlIGxhIGJhc2UgZGUgZGF0b3MgYGdhcG1pbmRlcmAuIAoKYGBge3IgZ3JhZmljb2xpbmVhfQpkYXRhKGdhcG1pbmRlciwgcGFja2FnZT0iZ2FwbWluZGVyIikKICAKIyBvYnRlbmVtb3MgZWwgcHJvbWVkaW8gZGUgdmlkYSBlbiBsYXMgQW3DqXJpY2FzCmFtZXJpY2FzIDwtIGdhcG1pbmRlciAlPiUKICBncm91cF9ieShjb250aW5lbnQsIHllYXIpICU+JQogIG11dGF0ZShsaWZlRXhwX21lYW4gPSBtZWFuKGxpZmVFeHApKSAlPiUKICBmaWx0ZXIoY29udGluZW50ID09ICJBbWVyaWNhcyIpICU+JQogIGRpc3RpbmN0KGNvbnRpbmVudCwgeWVhciwgLmtlZXBfYWxsPVQpICU+JQogIHVuZ3JvdXAoKQogIAoKIyBzaW1wbGUgZ3JhZmljbyBkZSBsw61uZWEKYW1lcmljYXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0geWVhciwgCiAgICAgICAgICAgeSA9IGxpZmVFeHBfbWVhbikpICsKICBnZW9tX3BvaW50KGNvbG9yPSJwZWFjaHB1ZmYiLCAKICAgICAgICAgICAgIHNpemUgPSAzLCAKICAgICAgICAgICAgIGFscGhhPS44KSArCiAgZ2VvbV9saW5lKGNvbG9yPSJwZXJ1IiwgCiAgICAgICAgICAgICBzaXplID0gMSwgCiAgICAgICAgICAgICBhbHBoYT0uNikgKwogIGxhYnMoeCA9ICJBw7FvIiwKICAgICAgIHkgPSAiRXhwZWN0YXRpdmEgZGUgVmlkYSIsCiAgICAgICB0aXRsZSA9ICJDYW1iaW9zIGVuIGxhIEV4cGVjdGF0aXZhIGRlIFZpZGEgYSBsbyBMYXJnbyBkZWwgVGllbXBvIiwKICAgICAgIHN1YnRpdGxlID0gIkxhcyBBbcOpcmljYXMgKDE5NTItMjAwNykiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKIyMjIENvbnRpbnVhcyB2cy4gQ2F0ZWfDs3JpY2EKCkN1YW5kbyBxdWVyZW1vcyBncmFmaWNhciBsYSByZWxhY2nDs24gZW50cmUgdW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhIHkgdW5hIGNvbnRpbnVhLCBoYXkgdmFyaW9zIHRpcG9zIGRlIGdyw6FmaWNvcyBkaXNwb25pYmxlcy4gRW50cmUgZXN0b3MgZXN0w6FuIGxvcyBncsOhZmljb3MgZGUgYmFycmFzLCBsb3MgZGVuc2l0eSBwbG90cywgYm94IHBsb3RzLCBldGMuIFZlYW1vcyBhbGd1bmFzIG9wY2lvbmVzLiAKCiMjIyMgR3LDoWZpY29zIGRlIEJhcnJhcyAyCgpFbiBsYSBzZWNjacOzbiBkZSBbR3LDoWZpY29zIGRlIEJhcnJhc10oI2dyw6FmaWNvcy1kZS1iYXJyYXMpIGxvcyB1dGlsaXphbW9zIHBhcmEgbW9zdHJhciBlbCBuw7ptZXJvIGRlIGNhc29zIGRlIHVuYSB2YXJpYWJsZSBvIGRlIGRvcyB2YXJpYWJsZXMuIFRhbWJpw6luIHNlIHB1ZWRlbiB1dGlsaXphciBncsOhZmljb3MgZGUgYmFycmFzIHBhcmEgbW9zdHJhciBlc3RhZMOtc3RpY2FzIGLDoXNpY2FzIChwb3IgZWouLCBwcm9tZWRpbyBvIG1lZGlhbmEpIGRlIHVuYSB2YXJpYWJsZSBjb250aW51YSBwYXJhIGNhZGEgbml2ZWwgZGUgdW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhLiAKClBvciBlamVtcGxvLCBlbCBzaWd1aWVudGUgZ3LDoWZpY28gbXVlc3RyYSBlbCBzYWxhcmlvIHByb21lZGlvIGRlIHByb2Zlc29yZXMgdW5pdmVyc2l0YXJpb3MgZW4gY2FkYSBwb3NpY2nDs24gYWNhZMOpbWljYS4gCgpgYGB7ciBiYXJwbG90Y2F0fQpsaWJyYXJ5KHNjYWxlcykgIyBwYXJhIHBvZGVyIHV0aWxpemFyIGRvbGxhcigpCgpTYWxhcmllcyAlPiUKICBtdXRhdGUocmFuayA9IHJlY29kZV9mYWN0b3IocmFuaywKICAgICAgICAgICAgICAgICAgICAgICBBc3N0UHJvZiA9ICJBc3Npc3RhbnQgUHJvZmVzc29yIiwgCiAgICAgICAgICAgICAgICAgICAgICAgQXNzb2NQcm9mID0gIkFzc29jaWF0ZSBQcm9mZXNzb3IiLAogICAgICAgICAgICAgICAgICAgICAgIFByb2YgPSAiUHJvZmVzc29yIikpICU+JQogICAgIyBjYWxjdWxhciBwcm9tZWRpb3MKICBncm91cF9ieShyYW5rKSAlPiUKICBtdXRhdGUobWVhbl9zYWxhcnkgPSBtZWFuKHNhbGFyeSkpICU+JQogIGRpc3RpbmN0KHJhbmssIC5rZWVwX2FsbD1UKSAlPiUKICAjIGdyYWZpY2FyCiAgZ2dwbG90KGFlcyh4ID0gcmFuaywgCiAgICAgICAgICAgeSA9IG1lYW5fc2FsYXJ5KSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBmaWxsID0gInBlYWNocHVmZiIsCiAgICAgICAgICAgY29sb3IgPSAicGVydSIsCiAgICAgICAgICAgYWxwaGEgPSAuNzUpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gZG9sbGFyKG1lYW5fc2FsYXJ5KSksIAogICAgICAgICAgICB2anVzdCA9IC0wLjMpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEzMDAwMCwgMjAwMDApLCAKICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBkb2xsYXIpICsKICBsYWJzKHRpdGxlID0gIlNhbGFyaW8gUHJvbWVkaW8gcG9yIFBvc2ljaW9uIiwgCiAgICAgICBzdWJ0aXRsZSA9ICJTYWxhcmlvIGRlIDkgbWVzZXMgZW50cmUgMjAwOCB5IDIwMDkiLAogICAgICAgeCA9ICIiLAogICAgICAgeSA9ICIiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKVW4gcHJvYmxlbWEgY29uIGVzdGUgdGlwbyBkZSBncsOhZmljb3MgZXMgcXVlIG5vIG11ZXN0cmFuIGxhIGRpc3RyaWJ1Y2lvbiBkZSBsYSBkYXRhLiBFbCBzaWd1aWVudGUgZ3LDoWZpY28gdHJhdGEgZGUgY29ycmVnaXIgZXNhIGxpbWl0YWNpw7NuLiAKCiMjIyMgR3LDoWZpY29zIGRlIERlbnNpZGFkIGRlIEdydXBvcwoKSGF5IHZhcmlhcyBtYW5lcmFzIGRlIHV0aWxpemFyIGxvcyBncsOhZmljb3MgZGUgZGVuc2lkYWQgcGFyYSBjb21wYXJhciBkaXN0cmlidWNpb25lcy4gQXF1w60gaGF5IHRyZXMuCgoxLiBEaXN0cmlidWNpb25lcyBzb2JyZWltcHVlc3RhczoKCmBgYHtyIGRlbnNpdHljYXR9ClNhbGFyaWVzICU+JQogIG11dGF0ZShyYW5rID0gcmVjb2RlX2ZhY3RvcihyYW5rLAogICAgICAgICAgICAgICAgICAgICAgIEFzc3RQcm9mID0gIkFzc2lzdGFudCBQcm9mZXNzb3IiLCAKICAgICAgICAgICAgICAgICAgICAgICBBc3NvY1Byb2YgPSAiQXNzb2NpYXRlIFByb2Zlc3NvciIsCiAgICAgICAgICAgICAgICAgICAgICAgUHJvZiA9ICJQcm9mZXNzb3IiKSkgJT4lCiAgIyBjYWxjdWxhciBwcm9tZWRpb3MKICBncm91cF9ieShyYW5rKSAlPiUKICBtdXRhdGUobWVhbl9zYWxhcnkgPSBtZWFuKHNhbGFyeSkpICU+JQogICMgZ3JhZmljYXIKICBnZ3Bsb3QoYWVzKHggPSBzYWxhcnksCiAgICAgICAgICAgICBmaWxsID0gcmFuaywKICAgICAgICAgICAgIGNvbG9yID0gcmFuaykpICsKICBnZW9tX2RlbnNpdHkoKSArIAogICMgRGlidWphciBsYXMgbMOtbmVhcyB2ZXJ0aWNhbGVzCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lYW5fc2FsYXJ5LCBjb2xvciA9IHJhbmspLCBsaW5ldHlwZT0iZGFzaGVkIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbj0iRSIsYWxwaGEgPSAwLjUpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uPSJFIixhbHBoYSA9IDAuODUpICsKICAgIGxhYnModGl0bGUgPSAiRGlzdHJpYnVjacOzbiBkZSBTYWxhcmlvIHBvciBQb3NpY2nDs24iLCAKICAgICAgIHN1YnRpdGxlID0gIlNhbGFyaW8gZGUgOSBtZXNlcyBlbnRyZSAyMDA4IHkgMjAwOSIsCiAgICAgICB4ID0gIlNhbGFyaW8iLAogICAgICAgeSA9ICIiICwKICAgICAgIGZpbGwgPSAiUG9zaWNpw7NuIiwKICAgICAgIGNvbG9yID0gIlBvc2ljacOzbiIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoyLiBEaXN0cmlidWNpb25lcyBlbiBgZmFjZXRzYDoKCmBgYHtyIGZhY2V0c2NhdH0KU2FsYXJpZXMgJT4lCiAgbXV0YXRlKHJhbmsgPSByZWNvZGVfZmFjdG9yKHJhbmssCiAgICAgICAgICAgICAgICAgICAgICAgQXNzdFByb2YgPSAiQXNzaXN0YW50IFByb2Zlc3NvciIsIAogICAgICAgICAgICAgICAgICAgICAgIEFzc29jUHJvZiA9ICJBc3NvY2lhdGUgUHJvZmVzc29yIiwKICAgICAgICAgICAgICAgICAgICAgICBQcm9mID0gIlByb2Zlc3NvciIpKSAlPiUKICAjIGNhbGN1bGFyIHByb21lZGlvcwogIGdyb3VwX2J5KHJhbmspICU+JQogIG11dGF0ZShtZWFuX3NhbGFyeSA9IG1lYW4oc2FsYXJ5KSkgJT4lCiAgIyBncmFmaWNhcgogIGdncGxvdChhZXMoeCA9IHNhbGFyeSwKICAgICAgICAgICAgIGZpbGwgPSByYW5rLAogICAgICAgICAgICAgY29sb3IgPSByYW5rKSkgKwogIGdlb21fZGVuc2l0eSgpICsgCiAgIyBEaWJ1amFyIGxhcyBsw61uZWFzIHZlcnRpY2FsZXMKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbl9zYWxhcnksIGNvbG9yID0gcmFuayksIGxpbmV0eXBlPSJkYXNoZWQiKSArCiAgIyBGYWNldHMKICBmYWNldF93cmFwKH5yYW5rLCBuY29sID0gMSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbj0iRSIsYWxwaGEgPSAwLjUpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uPSJFIixhbHBoYSA9IDAuODUpICsKICAgIGxhYnModGl0bGUgPSAiRGlzdHJpYnVjacOzbiBkZSBTYWxhcmlvIHBvciBQb3NpY2nDs24iLCAKICAgICAgIHN1YnRpdGxlID0gIlNhbGFyaW8gZGUgOSBtZXNlcyBlbnRyZSAyMDA4IHkgMjAwOSIsCiAgICAgICB4ID0gIlNhbGFyaW8iLAogICAgICAgeSA9ICIiICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgozLiBEaXN0cmlidWNpb25lcyBzb2JyZWltcHVlc3RhcyB5IGVuIGBmYWNldHNgOgoKYGBge3IgcmlkZ2VzfQpsaWJyYXJ5KGdncmlkZ2VzKQoKU2FsYXJpZXMgJT4lCiAgbXV0YXRlKHJhbmsgPSByZWNvZGVfZmFjdG9yKHJhbmssCiAgICAgICAgICAgICAgICAgICAgICAgQXNzdFByb2YgPSAiQXNzaXN0YW50IFByb2Zlc3NvciIsIAogICAgICAgICAgICAgICAgICAgICAgIEFzc29jUHJvZiA9ICJBc3NvY2lhdGUgUHJvZmVzc29yIiwKICAgICAgICAgICAgICAgICAgICAgICBQcm9mID0gIlByb2Zlc3NvciIpKSAlPiUKICAjIGNhbGN1bGFyIHByb21lZGlvcwogIGdyb3VwX2J5KHJhbmspICU+JQogIG11dGF0ZShtZWFuX3NhbGFyeSA9IG1lYW4oc2FsYXJ5KSkgJT4lCiAgIyBncmFmaWNhcgogIGdncGxvdChhZXMoeCA9IHNhbGFyeSwKICAgICAgICAgICAgIHkgPSByYW5rLAogICAgICAgICAgICAgZmlsbCA9IHJhbmssCiAgICAgICAgICAgICBjb2xvciA9IHJhbmspKSArCiAgZ2VvbV9kZW5zaXR5X3JpZGdlcygpICsgCiAgIyBEaWJ1amFyIGxhcyBsw61uZWFzIHZlcnRpY2FsZXMKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbl9zYWxhcnksIGNvbG9yID0gcmFuayksIGxpbmV0eXBlPSJkYXNoZWQiKSArCiAgIyBGYWNldHMKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb249IkUiLGFscGhhID0gMC41KSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbj0iRSIsYWxwaGEgPSAwLjg1KSArCiAgICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1Y2nDs24gZGUgU2FsYXJpbyBwb3IgUG9zaWNpw7NuIiwgCiAgICAgICBzdWJ0aXRsZSA9ICJTYWxhcmlvIGRlIDkgbWVzZXMgZW50cmUgMjAwOCB5IDIwMDkiLAogICAgICAgeCA9ICJTYWxhcmlvIiwKICAgICAgIHkgPSAiIiApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgojIyMjIEJveCBQbG90cwoKVW4gYm94IHBsb3QgbXVlc3RyYSBlbCBwZXJjZW50aWwgMjV0bywgbGEgbWVkaWEgeSBlbCBwZXJjZW50aWwgNzV0byBkZSB1bnMgZGlzdHJpYnVjacOzbi4gTG9zIGJpZ290ZXMgKHdoaXNrZXJzIG8gbMOtbmVhcyB2ZXJ0aWNhbGVzKSBjYXB1dHVyYW4gZWwgOTklIGRlIHVuYSBkaXN0cmlidWNpw7NuIG5vcm1hbCwgeSBsYSBvYnNlcnZhY2lvbmVzIHF1ZSBlc3TDoW4gZnVlcmEgZGUgZXNlIHJhbmdvIHNlIGdyYWZpY2EgY29tbyBwdW50b3MgcXVlIHJlcHJlc2VudGFuIG91dGxpZXJzICh2ZXIgbGEgRmlndXJhIGRlIGFiYWpvKS4gCgohW0VqZW1wbG8gZGUgYm94IHBsb3RdKGJveHBsb3QucG5nICJFamVtcGxvIGRlIGJveCBwbG90IikKClNpIHBvbmVtb3MgZG9zIGJveCBwbG90IHVubyBhbCBsYWRvIGRlbCBvdHJvLCBlc3RvcyByZXN1bHRhbiBtdXkgw7p0aWxlcyBwYXJhIGNvbXBhcmFyIGdydXBvcyBlbiB1bmEgdmFyaWFibGEgY29udGludWEuICAKCmBgYHtyIGJveHBsb3R9ClNhbGFyaWVzICU+JQogIG11dGF0ZShyYW5rID0gcmVjb2RlX2ZhY3RvcihyYW5rLAogICAgICAgICAgICAgICAgICAgICAgIEFzc3RQcm9mID0gIkFzc2lzdGFudCBQcm9mZXNzb3IiLCAKICAgICAgICAgICAgICAgICAgICAgICBBc3NvY1Byb2YgPSAiQXNzb2NpYXRlIFByb2Zlc3NvciIsCiAgICAgICAgICAgICAgICAgICAgICAgUHJvZiA9ICJQcm9mZXNzb3IiKSkgJT4lCiAgIyBncmFmaWNhcgogIGdncGxvdChhZXMoeSA9IHNhbGFyeSwKICAgICAgICAgICAgIHggPSByYW5rLAogICAgICAgICAgICAgZmlsbCA9IHJhbmssCiAgICAgICAgICAgICBjb2xvciA9IHJhbmspKSArCiAgIyBkaWJ1amFyIGJveHBsb3RzIAogIGdlb21fYm94cGxvdCgpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAzMDAwMDAsIDUwMDAwKSwgCiAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gZG9sbGFyKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uPSJFIixhbHBoYSA9IDAuNSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249IkUiLGFscGhhID0gMC44NSkgKwogICAgbGFicyh0aXRsZSA9ICJEaXN0cmlidWNpw7NuIGRlIFNhbGFyaW8gcG9yIFBvc2ljacOzbiIsIAogICAgICAgc3VidGl0bGUgPSAiU2FsYXJpbyBkZSA5IG1lc2VzIGVudHJlIDIwMDggeSAyMDA5IiwKICAgICAgIHggPSAiUG9zaWNpw7NuIiwKICAgICAgIHkgPSAiIiApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCmBgYAoKVW5hIGFsdGVybmF0aXZhIHF1ZSBjb21iaW5hIGVsIGJveCBwbG90IGNvbiBlbCBkZW5zaXR5IGRpc3RyaWJ1dGlvbiBlcyBlbCB2aW9saW4gcGxvdCAoZXMgdW4gcGxvdCBkZSBkZW5zaWRhZCByb3RhZG8gOTAgZ3JhZG9zIHkgZHVwbGljYWRvIGNvbW8gZXNwZWpvKS4gCgpgYGB7ciB2aW9saW5wbG90fQpTYWxhcmllcyAlPiUKICBtdXRhdGUocmFuayA9IHJlY29kZV9mYWN0b3IocmFuaywKICAgICAgICAgICAgICAgICAgICAgICBBc3N0UHJvZiA9ICJBc3Npc3RhbnQgUHJvZmVzc29yIiwgCiAgICAgICAgICAgICAgICAgICAgICAgQXNzb2NQcm9mID0gIkFzc29jaWF0ZSBQcm9mZXNzb3IiLAogICAgICAgICAgICAgICAgICAgICAgIFByb2YgPSAiUHJvZmVzc29yIikpICU+JQogICMgZ3JhZmljYXIKICBnZ3Bsb3QoYWVzKHkgPSBzYWxhcnksCiAgICAgICAgICAgICB4ID0gcmFuaywKICAgICAgICAgICAgIGZpbGwgPSByYW5rLAogICAgICAgICAgICAgY29sb3IgPSByYW5rKSkgKwogICMgZGlidWphciBib3hwbG90cyAKICBnZW9tX3Zpb2xpbigpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAzMDAwMDAsIDUwMDAwKSwgCiAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gZG9sbGFyKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uPSJFIixhbHBoYSA9IDAuNSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249IkUiLGFscGhhID0gMC44NSkgKwogICAgbGFicyh0aXRsZSA9ICJEaXN0cmlidWNpw7NuIGRlIFNhbGFyaW8gcG9yIFBvc2ljacOzbiIsIAogICAgICAgc3VidGl0bGUgPSAiU2FsYXJpbyBkZSA5IG1lc2VzIGVudHJlIDIwMDggeSAyMDA5IiwKICAgICAgIHggPSAiUG9zaWNpw7NuIiwKICAgICAgIHkgPSAiIiApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCmBgYAoKTywgbWVqb3IgYcO6biwgcHVlZGVuIGNvbWJpbmFyIGVsIGJveCBwbG90IGNvbiBlbCB2aW9saW4gcGxvdDoKCmBgYHtyIHZpb2xpbmJveHBsb3R9ClNhbGFyaWVzICU+JQogIG11dGF0ZShyYW5rID0gcmVjb2RlX2ZhY3RvcihyYW5rLAogICAgICAgICAgICAgICAgICAgICAgIEFzc3RQcm9mID0gIkFzc2lzdGFudCBQcm9mZXNzb3IiLCAKICAgICAgICAgICAgICAgICAgICAgICBBc3NvY1Byb2YgPSAiQXNzb2NpYXRlIFByb2Zlc3NvciIsCiAgICAgICAgICAgICAgICAgICAgICAgUHJvZiA9ICJQcm9mZXNzb3IiKSkgJT4lCiAgIyBncmFmaWNhcgogIGdncGxvdChhZXMoeSA9IHNhbGFyeSwKICAgICAgICAgICAgIHggPSByYW5rLAogICAgICAgICAgICAgZmlsbCA9IHJhbmssCiAgICAgICAgICAgICBjb2xvciA9IHJhbmspKSArCiAgIyBkaWJ1amFyIGJveHBsb3RzIAogIGdlb21fdmlvbGluKCkgKyAKICBnZW9tX2JveHBsb3Qod2lkdGggPSAuMTUsIAogICAgICAgICAgICAgICBvdXRsaWVyLnNpemUgPSAyKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAzMDAwMDAsIDUwMDAwKSwgCiAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gZG9sbGFyKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uPSJFIixhbHBoYSA9IDAuNSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249IkUiLGFscGhhID0gMC44NSkgKwogICAgbGFicyh0aXRsZSA9ICJEaXN0cmlidWNpw7NuIGRlIFNhbGFyaW8gcG9yIFBvc2ljacOzbiIsIAogICAgICAgc3VidGl0bGUgPSAiU2FsYXJpbyBkZSA5IG1lc2VzIGVudHJlIDIwMDggeSAyMDA5IiwKICAgICAgIHggPSAiUG9zaWNpw7NuIiwKICAgICAgIHkgPSAiIiApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCmBgYAoKIyMgR3LDoWZpY29zIE11bHRpdmFyaWFkb3MKClBhcmEgZ3JhZmljYXIgbGEgcmVsYWNpw7NuIGVudHJlIHRyZXMgbyBtw6FzIHZhcmlhYmxlcyAoYXVucXVlIHlhIHRyZXMgZXMgbXVjaG8pLCBwb2RlbW9zIHV0aWxpemFyIGRvcyBoZXJyYW1pZW50YXMgcXVlIHlhIHVzYW1vcyBwcmV2aWFtZW50ZTogZ3JvdXBpbmcgeSBmYWNldGluZy4gCgojIyMgR3JvdXBpbmcKClBvZGVtb3MgcmV0b21hciBlbCBbc2NhdHRlciBwbG90XSgjc2NhdHRlcnBsb3QyKSBxdWUgaGljaW1vcyBhbnRlcyB5IGFncmVnYXJsZSB1bmEgY2F0ZWdvcsOtYSBtw6FzOgoKYGBge3Igc2NhdHRlcnBsb3RjYXR9CgojIHNjYXR0ZXJwbG90IHBvciBjYXRlZ29yaWEKU2FsYXJpZXMgJT4lCiAgbXV0YXRlKHJhbmsgPSByZWNvZGVfZmFjdG9yKHJhbmssCiAgICAgICAgICAgICAgICAgICAgICAgQXNzdFByb2YgPSAiQXNzaXN0YW50IFByb2Zlc3NvciIsIAogICAgICAgICAgICAgICAgICAgICAgIEFzc29jUHJvZiA9ICJBc3NvY2lhdGUgUHJvZmVzc29yIiwKICAgICAgICAgICAgICAgICAgICAgICBQcm9mID0gIlByb2Zlc3NvciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB5cnMuc2luY2UucGhkLCAKICAgICAgICAgICB5ID0gc2FsYXJ5LAogICAgICAgICAgIGNvbG9yID0gcmFuaykpICsKICBnZW9tX3BvaW50KHNpemUgPSAyKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbj0iRSIsYWxwaGEgPSAwLjg1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVsID0gc2NhbGVzOjpkb2xsYXIsIAogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDUwMDAwLCAyNTAwMDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA2MCwgMTApLCAKICAgICAgICAgICAgICAgICAgICAgbGltaXRzPWMoMCwgNjApKSArIAogIGxhYnMoeCA9ICJBw7FvcyBkZXNkZSBlbCBQaEQiLAogICAgICAgeSA9ICIiLAogICAgICAgdGl0bGUgPSAiRXhwZXJpZW5jaWEgdnMuIFNhbGFyaW8iLAogICAgICAgc3VidGl0bGUgPSAiU2FsYXJpbyBkZSA5IG1lc2VzIGVudHJlIDIwMDggeSAyMDA5IiwKICAgICAgIGNvbG9yID0gIlBvc2ljacOzbiIpICsKICB0aGVtZV9taW5pbWFsKCkKCmBgYAoKQWhvcmEsIHBvZGVtb3MgaW5jbHVzbyBhZ3JlZ2FyIHVuYSBkaW1lbnNpw7NuIG3DoXMgKGfDqW5lcm8pIG1vZGlmaWNhbmRvIGxhIGZvcm1hIGRlIGNhZGEgcHVudG86CgpgYGB7ciBzY2F0dGVycGxvdGNhdDJ9CgojIHNjYXR0ZXJwbG90IHBvciBjYXRlZ29yaWEKU2FsYXJpZXMgJT4lCiAgbXV0YXRlKHJhbmsgPSByZWNvZGVfZmFjdG9yKHJhbmssCiAgICAgICAgICAgICAgICAgICAgICAgQXNzdFByb2YgPSAiQXNzaXN0YW50IFByb2Zlc3NvciIsIAogICAgICAgICAgICAgICAgICAgICAgIEFzc29jUHJvZiA9ICJBc3NvY2lhdGUgUHJvZmVzc29yIiwKICAgICAgICAgICAgICAgICAgICAgICBQcm9mID0gIlByb2Zlc3NvciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB5cnMuc2luY2UucGhkLCAKICAgICAgICAgICB5ID0gc2FsYXJ5LAogICAgICAgICAgIGNvbG9yID0gcmFuaywKICAgICAgICAgICBzaGFwZSA9IHNleCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAyKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbj0iRSIsYWxwaGEgPSAwLjg1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVsID0gc2NhbGVzOjpkb2xsYXIsIAogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDUwMDAwLCAyNTAwMDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA2MCwgMTApLCAKICAgICAgICAgICAgICAgICAgICAgbGltaXRzPWMoMCwgNjApKSArIAogIGxhYnMoeCA9ICJBw7FvcyBkZXNkZSBlbCBQaEQiLAogICAgICAgeSA9ICIiLAogICAgICAgdGl0bGUgPSAiRXhwZXJpZW5jaWEgdnMuIFNhbGFyaW8iLAogICAgICAgc3VidGl0bGUgPSAiU2FsYXJpbyBkZSA5IG1lc2VzIGVudHJlIDIwMDggeSAyMDA5IiwKICAgICAgIGNvbG9yID0gIlBvc2ljacOzbiIsCiAgICAgICBzaGFwZSA9ICJHw6luZXJvIikgKwogIHRoZW1lX21pbmltYWwoKQoKYGBgCgpPIHBvZGVtb3MgdXNhciBvdHJhIGRpbWVuc2nDs24gcGFyYSByZXNhbHRhciBlbCBlamUgeCBvIGVsIGVqZSB5LiBQb3IgZWplbXBsbywgcG9kZW1vcyByZXNhbHRhciBlbCBzYWxhcmlvOgoKYGBge3Igc2NhdHRlcnBsb3RjYXQzfQoKIyBzY2F0dGVycGxvdCBwb3IgY2F0ZWdvcmlhIHJlc2FsdGFuZG8gc2FsYXJpbwpTYWxhcmllcyAlPiUKICBtdXRhdGUocmFuayA9IHJlY29kZV9mYWN0b3IocmFuaywKICAgICAgICAgICAgICAgICAgICAgICBBc3N0UHJvZiA9ICJBc3Npc3RhbnQgUHJvZmVzc29yIiwgCiAgICAgICAgICAgICAgICAgICAgICAgQXNzb2NQcm9mID0gIkFzc29jaWF0ZSBQcm9mZXNzb3IiLAogICAgICAgICAgICAgICAgICAgICAgIFByb2YgPSAiUHJvZmVzc29yIikpICU+JQogIGdncGxvdChhZXMoeCA9IHlycy5zaW5jZS5waGQsIAogICAgICAgICAgIHkgPSBzYWxhcnksCiAgICAgICAgICAgY29sb3IgPSByYW5rLAogICAgICAgICAgIHNpemUgPSBzYWxhcnkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uPSJFIixhbHBoYSA9IDAuNjUpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWwgPSBzY2FsZXM6OmRvbGxhciwgCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoNTAwMDAsIDI1MDAwMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDYwLCAxMCksIAogICAgICAgICAgICAgICAgICAgICBsaW1pdHM9YygwLCA2MCkpICsgCiAgbGFicyh4ID0gIkHDsW9zIGRlc2RlIGVsIFBoRCIsCiAgICAgICB5ID0gIiIsCiAgICAgICB0aXRsZSA9ICJFeHBlcmllbmNpYSB2cy4gU2FsYXJpbyIsCiAgICAgICBzdWJ0aXRsZSA9ICJTYWxhcmlvIGRlIDkgbWVzZXMgZW50cmUgMjAwOCB5IDIwMDkiLAogICAgICAgY29sb3IgPSAiUG9zaWNpw7NuIiwKICAgICAgIHNpemUgPSAiU2FsYXJpbyIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpZIHJlc2FsdGFuZG8gZWwgZWplIHggKGV4cGVyaWVuY2lhKSBzZSB2ZXLDrWEgYXPDrToKCmBgYHtyIHNjYXR0ZXJwbG90Y2F0NH0KCiMgc2NhdHRlcnBsb3QgcG9yIGNhdGVnb3JpYSByZXNhbHRhbmRvIHNhbGFyaW8KU2FsYXJpZXMgJT4lCiAgbXV0YXRlKHJhbmsgPSByZWNvZGVfZmFjdG9yKHJhbmssCiAgICAgICAgICAgICAgICAgICAgICAgQXNzdFByb2YgPSAiQXNzaXN0YW50IFByb2Zlc3NvciIsIAogICAgICAgICAgICAgICAgICAgICAgIEFzc29jUHJvZiA9ICJBc3NvY2lhdGUgUHJvZmVzc29yIiwKICAgICAgICAgICAgICAgICAgICAgICBQcm9mID0gIlByb2Zlc3NvciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB5cnMuc2luY2UucGhkLCAKICAgICAgICAgICB5ID0gc2FsYXJ5LAogICAgICAgICAgIGNvbG9yID0gcmFuaywKICAgICAgICAgICBzaXplID0geXJzLnNpbmNlLnBoZCkpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249IkUiLGFscGhhID0gMC41KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVsID0gc2NhbGVzOjpkb2xsYXIsIAogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDUwMDAwLCAyNTAwMDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA2MCwgMTApLCAKICAgICAgICAgICAgICAgICAgICAgbGltaXRzPWMoMCwgNjApKSArIAogIGxhYnMoeCA9ICJBw7FvcyBkZXNkZSBlbCBQaEQiLAogICAgICAgeSA9ICIiLAogICAgICAgdGl0bGUgPSAiRXhwZXJpZW5jaWEgdnMuIFNhbGFyaW8iLAogICAgICAgc3VidGl0bGUgPSAiU2FsYXJpbyBkZSA5IG1lc2VzIGVudHJlIDIwMDggeSAyMDA5IiwKICAgICAgIGNvbG9yID0gIlBvc2ljacOzbiIsCiAgICAgICBzaXplID0gIkV4cGVyaWVuY2lhIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCkZpbmFsbWVudGUsIHBvZGVtb3MgaGFjZXIgbGFzIG1pc21hIGJlc3QgZml0IGxpbmVzLCBwZXJvIGVzdGEgdmV6IHBvciBjYXRlZ29yw61hLiAKYGBge3Igc2NhdHRlcmNhdGJlc3RmaXR9CgojIHNjYXR0ZXJwbG90IHBvciBjYXRlZ29yaWEKU2FsYXJpZXMgJT4lCiAgbXV0YXRlKHJhbmsgPSByZWNvZGVfZmFjdG9yKHJhbmssCiAgICAgICAgICAgICAgICAgICAgICAgQXNzdFByb2YgPSAiQXNzaXN0YW50IFByb2Zlc3NvciIsIAogICAgICAgICAgICAgICAgICAgICAgIEFzc29jUHJvZiA9ICJBc3NvY2lhdGUgUHJvZmVzc29yIiwKICAgICAgICAgICAgICAgICAgICAgICBQcm9mID0gIlByb2Zlc3NvciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB5cnMuc2luY2UucGhkLCAKICAgICAgICAgICB5ID0gc2FsYXJ5LAogICAgICAgICAgIGNvbG9yID0gcmFuaywKICAgICAgICAgICBmaWxsID0gcmFuaykpICsKICBnZW9tX3BvaW50KHNpemUgPSAyKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwKICAgICAgICAgICAgICBmb3JtdWxhID0geSB+IHBvbHkoeCwgMikpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uPSJFIixhbHBoYSA9IDAuNSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbj0iRSIsYWxwaGEgPSAwLjY1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVsID0gc2NhbGVzOjpkb2xsYXIsIAogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDUwMDAwLCAyNTAwMDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA2MCwgMTApLCAKICAgICAgICAgICAgICAgICAgICAgbGltaXRzPWMoMCwgNjApKSArIAogIGxhYnMoeCA9ICJBw7FvcyBkZXNkZSBlbCBQaEQiLAogICAgICAgeSA9ICIiLAogICAgICAgdGl0bGUgPSAiRXhwZXJpZW5jaWEgdnMuIFNhbGFyaW8iLAogICAgICAgc3VidGl0bGUgPSAiU2FsYXJpbyBkZSA5IG1lc2VzIGVudHJlIDIwMDggeSAyMDA5IiwKICAgICAgIGNvbG9yID0gIlBvc2ljacOzbiIsCiAgICAgICBmaWxsID0gIlBvc2ljacOzbiIpICsKICB0aGVtZV9taW5pbWFsKCkKCmBgYAoKIyMjIEZhY2V0aW5nCgpNaWVudHJhcyBncm91cGluZyB0ZSBwZXJtaXRlIHZpc3VhbGl6YXIgdG9kYXMgbGFzIGRpbWVuc2lvbmVzIHkgY2F0ZWdvcsOtYXMgZW4gdW4gc29sbyBncsOhZmljbywgY29uIGZhY2V0aW5nIHB1ZWRlcyBkaXZpZGlyIGVuIHN1Ymdyw6FmaWNvcyB0dSBkYXRhLiBDYWRhIHN1Ymdyw6FmaWNvIGVzdGFyw6EgbGltaXRhZG8gYSBsYSBjYXRlZ29yw61hIHF1ZSBlbGlqYXMuIFBvciBlamVtcGxvLCBwb2RlbW9zIHJlcGxpY2FyIGVsIGdyw6FmaWNvIGFudGVyaW9yLCBwZXJvIGVuIGx1Z2FyIGRlIGhhY2VyIGdyb3VwaW5nIHBvciBwb3NpY2nDs24sIHBvZGVtb3MgaGFjZXIgZmFjZXRpbmcgcG9yIHBvc2ljacOzbi4gCgpgYGB7ciBmYWNldDF9CgojIHNjYXR0ZXJwbG90IHBvciBjYXRlZ29yaWEKU2FsYXJpZXMgJT4lCiAgbXV0YXRlKHJhbmsgPSByZWNvZGVfZmFjdG9yKHJhbmssCiAgICAgICAgICAgICAgICAgICAgICAgQXNzdFByb2YgPSAiQXNzaXN0YW50IFByb2Zlc3NvciIsIAogICAgICAgICAgICAgICAgICAgICAgIEFzc29jUHJvZiA9ICJBc3NvY2lhdGUgUHJvZmVzc29yIiwKICAgICAgICAgICAgICAgICAgICAgICBQcm9mID0gIlByb2Zlc3NvciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB5cnMuc2luY2UucGhkLCAKICAgICAgICAgICB5ID0gc2FsYXJ5LAogICAgICAgICAgICMgTWFudGVuZ28gZXN0byBwb3IgZXN0w6l0aWNhCiAgICAgICAgICAgY29sb3IgPSByYW5rLAogICAgICAgICAgIGZpbGwgPSByYW5rKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLAogICAgICAgICAgICAgIGZvcm11bGEgPSB5IH4gcG9seSh4LCAyKSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249IkUiLGFscGhhID0gMC41KSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uPSJFIixhbHBoYSA9IDAuNjUpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWwgPSBzY2FsZXM6OmRvbGxhciwgCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoNTAwMDAsIDI1MDAwMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDYwLCAxMCksIAogICAgICAgICAgICAgICAgICAgICBsaW1pdHM9YygwLCA2MCkpICsgCiAgZmFjZXRfd3JhcCh+IHJhbmssIG5jb2wgPSAxKSArCiAgbGFicyh4ID0gIkHDsW9zIGRlc2RlIGVsIFBoRCIsCiAgICAgICB5ID0gIiIsCiAgICAgICB0aXRsZSA9ICJFeHBlcmllbmNpYSB2cy4gU2FsYXJpbyIsCiAgICAgICBzdWJ0aXRsZSA9ICJTYWxhcmlvIGRlIDkgbWVzZXMgZW50cmUgMjAwOCB5IDIwMDkiLAogICAgICAgY29sb3IgPSAiUG9zaWNpw7NuIiwKICAgICAgIGZpbGwgPSAiUG9zaWNpw7NuIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKYGBgCgpQb2RlbW9zIGFncmVnYXIgZmFjZXRzIHBvciBtw6FzIGRlIHVuYSBjYXRlZ29yw61hLiBFbCBncsOhZmljbyBhbnRlcmlvciBsZSBwb2RlbW9zIGFncmVnYXIgdW4gZmFjZXQgZGUgZ8OpbmVybyB0YW1iacOpbi4gCgpgYGB7ciBmYWNldDJ9CgojIHNjYXR0ZXJwbG90IHBvciBjYXRlZ29yaWEKU2FsYXJpZXMgJT4lCiAgbXV0YXRlKHJhbmsgPSByZWNvZGVfZmFjdG9yKHJhbmssCiAgICAgICAgICAgICAgICAgICAgICAgQXNzdFByb2YgPSAiQXNzaXN0YW50IFByb2Zlc3NvciIsIAogICAgICAgICAgICAgICAgICAgICAgIEFzc29jUHJvZiA9ICJBc3NvY2lhdGUgUHJvZmVzc29yIiwKICAgICAgICAgICAgICAgICAgICAgICBQcm9mID0gIlByb2Zlc3NvciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB5cnMuc2luY2UucGhkLCAKICAgICAgICAgICB5ID0gc2FsYXJ5LAogICAgICAgICAgICMgTWFudGVuZ28gZXN0byBwb3IgZXN0w6l0aWNhCiAgICAgICAgICAgY29sb3IgPSByYW5rLAogICAgICAgICAgIGZpbGwgPSByYW5rKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsKICAjIENhbWJpYW1vcyBhIGxpbmVhbCBwb3JxdWUgaGF5IG1lbm9zIGRhdGEgcG9yIGZhY2V0CiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwKICAgICAgICAgICAgICBmb3JtdWxhID0geSB+IHBvbHkoeCwgMSkpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uPSJFIixhbHBoYSA9IDAuNSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbj0iRSIsYWxwaGEgPSAwLjY1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVsID0gc2NhbGVzOjpkb2xsYXIpICsKICAjIEFncmVnbyBzY2FsZXMgPSAiZnJlZSIgcGFyYSBxdWUgc2UgbXVldmFuIGxpYnJlbWVudGUgbG9zIGVqZXMuCiAgZmFjZXRfd3JhcChzZXggfiByYW5rLCBzY2FsZXMgPSAiZnJlZSIpICsKICBsYWJzKHggPSAiQcOxb3MgZGVzZGUgZWwgUGhEIiwKICAgICAgIHkgPSAiIiwKICAgICAgIHRpdGxlID0gIkV4cGVyaWVuY2lhIHZzLiBTYWxhcmlvIiwKICAgICAgIHN1YnRpdGxlID0gIlNhbGFyaW8gZGUgOSBtZXNlcyBlbnRyZSAyMDA4IHkgMjAwOSIsCiAgICAgICBjb2xvciA9ICJQb3NpY2nDs24iLAogICAgICAgZmlsbCA9ICJQb3NpY2nDs24iKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKRmluYWxtZW50ZSwgdmFtb3MgYSByZWdyZWFyIGEgbnVlc3RyYSBbbMOtbmVhIGRlIHRpZW1wb10oI2dyw6FmaWNvLWRlLWzDrW5lYSkgeSwgZW4gbHVnYXIgZGUgaGFjZXIgdW4gc29sbyBncsOhZmljbyBkZSBsYSBleHBlY3RhdGl2YSBkZSB2aWRhIHBhcmEgdG9kbyBlbCBjb250aW5lbnRlLCBwb2RlbW9zIGhhY2VyIHVubyBwb3IgcGHDrXMsIHV0aWxpemFuZG8gZmFjZXRzOgoKYGBge3IgbGluZWFmYWNldH0KZ2FwbWluZGVyICU+JQogIGZpbHRlcihjb250aW5lbnQgPT0gIkFtZXJpY2FzIikgJT4lCiAgZ2dwbG90KGFlcyh4PXllYXIsIHkgPSBsaWZlRXhwKSkgKwogIGdlb21fbGluZShjb2xvcj0icGVhY2hwdWZmIikgKwogIGdlb21fcG9pbnQoY29sb3I9InBlcnUiKSArCiAgZmFjZXRfd3JhcCh+Y291bnRyeSkgKyAKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDkpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEpKSArCiAgbGFicyh0aXRsZSA9ICJFdm9sdWNpw7NuIGRlIGxhIEV4cGVjdGF0aXZhIGRlIFZpZGEiLAogICAgICAgeCA9ICJBw7FvIiwKICAgICAgIHkgPSAiRXhwZWN0YXRpdmEgZGUgVmlkYSIpIApgYGAKClBvZGVtb3Mgb2JzZXJ2YXIgY29tbyBlbiB0b2RvIGxvcyBwYcOtc2VzIGxhIGV4cGVjdGF0aXZhIGRlIHZpZGEgc3ViZSwgc2Fsdm8gZW4gRWwgU2FsdmFkb3IgZW50cmUgMTk3MCB5IDE5OTAuIAoKIyBDb25jbHVzacOzbjoKCsK/SGF5IG3DoXMgZ3LDoWZpY29zIHF1ZSBzZSBwdWVkZW4gaGFjZXI/IExvcyBoYXkuIFBlcm8gbGEgw7puaWNhIG1hbmVyYSBkZSBzYWJlciBxdcOpIGdyw6FmaWNvcyBuZWNlc2l0YSBzdSBoaXN0b3JpYSBlcyB0cmFiYWphbmRvIHkgY29ub2NpZW5kbyBzdSBkYXRhLiBSZWN1ZXJkZW4gcXVlIGxhIHZpc3VhbGl6YWNpw7NuIGRlIGxhIGRhdGEgdGllbmUgcXVlIGNvbXVuaWNhciB1biBwdW50byBkZSBtYW5lcmEgZWZlY3RpdmEuIERlbWFzaWFkYSBpbmZvcm1hY2nDs24sIHkgZWwgbGVjdG9yIG5vIHNhYnLDoSBxdcOpIHByaW9yaXphciBlbiBlbCBncsOhZmljby4gTXV5IHBvY28gaW5mb3JtYWNpw7NuLCB5IGVsIGxlY3RvciBubyBzYWJyw6Egc29icmUgcXXDqSBlcyBlbCBncsOhZmljby4gRWwgYmFycm9jbyBlcyBlbmVtaWdvIGRlIGxhIHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zOgoKIVtFbCBiYXJyb2NvIGVzIGVuZW1pZ28gZGUgbGEgdmlzdWFsaXphY2nDs24gZGUgZGF0b3NdKG1lbWUucG5nICJFbCBiYXJyb2NvIGVzIGVuZW1pZ28gZGUgbGEgdmlzdWFsaXphY2nDs24gZGUgZGF0b3MiKQoKRmluYWxtZW50ZSwgY3VhbHF1aWVyIHByZWd1bnRhIHF1ZSBwdWVkYW4gdGVuZXIgYWNlcmNhIGRlIGxhIHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zLCBzZWd1cmFtZW50ZSBwdWVkZW4gZW5jb250cmFyIGxhIHJlc3B1ZXN0YSBlbiBlbCBpbnRlcm5ldC4gQWhvcmEgc8OtLCB2YXlhbiBhbCBtdW5kbyB5IGdyYWZpcXVlbiEhCgoKIVtVbiBidWVuIGdyw6FmaWNvIHF1ZSBpbHVzdHJhIHN1IHB1bnRvXShkb2djYXQucG5nICJVbiBidWVuIGdyw6FmaWNvIHF1ZSBpbHVzdHJhIHN1IHB1bnRvIikKCgoKCgo=